All Files (68.77% covered at 153.49 hits/line)
166 files in total.
7893 relevant lines.
5428 lines covered and
2465 lines missed
-
1
class ApplicationController < ActionController::Base
-
# Prevent CSRF attacks by raising an exception.
-
# For APIs, you may want to use :null_session instead.
-
1
protect_from_forgery with: :exception
-
#コントローラー内で、ヘルパーを使用するためのinclude宣言
-
1
include SessionsHelper
-
#messageかmicropostのreplyか判断
-
1
include MessagesHelper
-
end
-
1
class MessagesController < ApplicationController
-
1
before_action :signed_in_user
-
1
before_action :correct_user, only: :destroy
-
-
#会話の中身を見る
-
1
def show
-
7
@message = current_user.messages.build(sender_id: current_user.id, reciptient_id: params[:id])
-
7
@messages = Message.find_conversation(current_user.id,params[:id])
-
7
if @messages.empty?
-
1
flash[:error] = "メッセージがないか、相手がいません"
-
1
redirect_to users_path
-
end
-
end
-
-
#メッセージ作成
-
1
def create
-
3
@message = Message.new(message_params)
-
3
if @message.save
-
1
flash[:success] = "success"
-
else
-
2
flash[:error] = "error"
-
end
-
3
redirect_to message_path(@message.reciptient_user)
-
end
-
-
1
def destroy
-
reciptient_user = @message.reciptient_user
-
@message.destroy
-
redirect_to message_path(reciptient_user)
-
end
-
-
1
private
-
-
1
def correct_user
-
#findでは、値がない場合例外が発生するため find_byにしてnilを渡させる
-
@message = current_user.messages.find_by(id: params[:id])
-
redirect_to root_url if @message.nil?
-
end
-
-
1
def message_params
-
3
params.require(:message).permit(:content, :sender_id, :reciptient_id)
-
end
-
end
-
1
class MicropostsController < ApplicationController
-
1
before_action :signed_in_user
-
1
before_action :correct_user, only: :destroy
-
-
1
def create
-
4
@message_or_micropost = create_model(micropost_params)
-
4
if @message_or_micropost.save
-
2
flash[:success] = "success"
-
else
-
2
flash[:error] = "error"
-
end
-
4
redirect_to root_url
-
end
-
-
1
def destroy
-
1
@micropost.destroy
-
1
redirect_to root_url
-
end
-
-
1
private
-
-
1
def micropost_params
-
4
params.require(:micropost).permit(:content)
-
end
-
-
1
def correct_user
-
#findでは、値がない場合例外が発生するため find_byにしてnilを渡させる
-
1
@micropost = current_user.microposts.find_by(id: params[:id])
-
1
redirect_to root_url if @micropost.nil?
-
end
-
-
1
def find_recipient_user(nick_name)
-
1
User.find_by(nickname: nick_name)
-
end
-
-
1
def create_model(micropost)
-
4
if reply_to_user_name = micropost[:content].match(/^d(\s+?)@([\w+-.]*)/i)
-
reply_to_user = find_recipient_user(reply_to_user_name[2])
-
@message = Message.new(sender_id: current_user.id,reciptient_id: reply_to_user.id ,content: micropost[:content]) if reply_to_user
-
else
-
4
reply_to_user_name = micropost[:content].match(/^@([\w+-.]+)/i)
-
4
micropost["in_reply_to"] = find_recipient_user(reply_to_user_name[1]) if reply_to_user_name
-
4
@micropost = current_user.microposts.build(micropost)
-
end
-
end
-
end
-
1
class RelationshipsController < ApplicationController
-
1
before_action :signed_in_user
-
-
1
def create
-
5
@user = User.find(params[:relationship][:followed_id])
-
5
current_user.follow!(@user)
-
5
respond_to do |format|
-
8
format.html { redirect_to @user }
-
5
format.js
-
end
-
end
-
-
1
def destroy
-
5
@user = Relationship.find(params[:id]).followed
-
5
current_user.unfollow!(@user)
-
5
respond_to do |format|
-
8
format.html { redirect_to @user }
-
5
format.js
-
end
-
end
-
end
-
1
class SessionsController < ApplicationController
-
1
def new
-
end
-
-
1
def create
-
#メールアドレスは、小文字で保存させているので、ここでdowncaseにして正しいかの検出を行う.
-
#form_tagによりパラメータ変更
-
58
user = User.find_by(email: params[:email].downcase)
-
58
if user && user.authenticate(params[:password])
-
#ユーザをサインインさせ、ユーザページ(show)にリダイレクトする
-
55
sign_in user
-
55
redirect_back_or root_url
-
else
-
#エラーメッセージを表示し、サインインフォームを再描画する
-
3
flash.now[:error] = 'Invalid email/password combination'
-
3
render 'new'
-
end
-
end
-
-
1
def destroy
-
1
sign_out
-
1
redirect_to root_url
-
end
-
end
-
1
class StaticPagesController < ApplicationController
-
1
def home
-
81
if signed_in?
-
70
@micropost = current_user.microposts.build
-
70
@feed_items = current_user.feed.paginate(page: params[:page], :per_page => 10)
-
end
-
end
-
-
1
def help
-
end
-
-
1
def about
-
end
-
-
1
def contact
-
end
-
end
-
1
class UsersController < ApplicationController
-
#ユーザにサインインを要求するために以下メソッドを定義して呼び出す
-
#only ではアクションを限定している
-
1
before_action :signed_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
-
1
before_action :correct_user, only: [:edit, :update]
-
1
before_action :admin_user, only: :destroy
-
1
skip_before_filter :verify_authenticity_token
-
-
-
1
def index
-
13
@users = User.paginate(page: params[:page])
-
end
-
-
1
def new
-
13
@user = User.new
-
end
-
-
1
def show
-
33
@user = User.find(params[:id])
-
33
@microposts = @user.microposts.paginate(page: params[:page])
-
33
if params[:format] == "xml"
-
access_token = JSON.parse(request.headers[:HTTP_AUTHORIZATION])
-
if have_api_key?(access_token)
-
render :xml => @user
-
else
-
head :bad_request
-
end
-
33
elsif params[:format] == nil
-
33
respond_to do |format|
-
33
format.html
-
end
-
else
-
head :bad_request
-
end
-
end
-
-
1
def create
-
11
@user = User.new(user_params)
-
11
if @user.save
-
5
sign_in @user
-
5
flash[:success] = "Welcome to the Sample App!"
-
5
redirect_to @user
-
else
-
6
render 'new'
-
end
-
end
-
-
1
def destroy
-
1
User.find(params[:id]).destroy
-
1
flash[:success] = "User destroyed."
-
1
redirect_to users_url
-
end
-
-
1
def edit
-
end
-
-
1
def update
-
#update_attributes属性を更新するメソッド
-
6
if @user.update_attributes(user_update_params)
-
#更新に成功した場合に扱う
-
5
flash[:success] = "Profile updated"
-
5
redirect_to @user
-
else
-
1
render 'edit'
-
end
-
end
-
-
1
def following
-
3
@title = "Following"
-
3
@user = User.find(params[:id])
-
3
@users = @user.followed_users.paginate(page: params[:page])
-
3
render 'show_follow'
-
end
-
-
1
def followers
-
3
@title = "Followers"
-
3
@user = User.find(params[:id])
-
3
@users = @user.followers.paginate(page: params[:page])
-
3
render 'show_follow'
-
end
-
-
1
private
-
-
#ここにadminを入れないことにより任意のユーザが自分自身にアプリケーションの管理者権限を与えることを防止している
-
1
def user_params
-
11
params.require(:user).permit(:name, :email, :nickname, :password, :password_confirmation)
-
end
-
-
1
def user_update_params
-
6
params.require(:user).permit(:name, :email, :password, :password_confirmation)
-
end
-
-
1
def correct_user
-
20
@user = User.find(params[:id])
-
20
redirect_to(root_path) unless current_user?(@user)
-
end
-
-
#管理者か確認
-
1
def admin_user
-
2
redirect_to(root_path) unless current_user.admin?
-
end
-
end
-
1
require 'active_support'
-
1
module Content
-
1
extend ActiveSupport::Concern
-
-
1
def find_recipient_user(word)
-
User.find_by(nickname: word)
-
end
-
end
-
1
class Message < ActiveRecord::Base
-
1
belongs_to :sender_user, class_name: "User", foreign_key: "sender_id"
-
1
belongs_to :reciptient_user, class_name: "User", foreign_key: "reciptient_id"
-
#降順 new→oldの順
-
52
default_scope -> { order('created_at DESC') }
-
-
1
validates :content, presence: true, length: {maximum: 140 }
-
1
validates :sender_id, presence: true
-
1
validates :reciptient_id, presence: true
-
-
1
def self.find_conversation(sender_id, reciptient_id)
-
7
where("(sender_id = :sender_id AND reciptient_id = :reciptient_id) OR (sender_id = :reciptient_id AND reciptient_id = :sender_id)", sender_id: sender_id, reciptient_id: reciptient_id)
-
end
-
end
-
1
class Micropost < ActiveRecord::Base
-
1
include Content
-
1
belongs_to :user
-
1
belongs_to :in_reply_to, class_name: "User", foreign_key: "in_reply_to"
-
#降順 new→oldの順
-
473
default_scope -> { order('created_at DESC') }
-
76
scope :including_replies, lambda { |user| from_users_followed_by(user) }
-
-
-
1
validates :content, presence: true, length: {maximum: 140 }
-
1
validates :user_id, presence: true
-
-
# 以下のマイクロポストを表示する
-
# 1.自分のマイクロソフト, 2.フォローしているユーザー達のマイクロポスト, 3.replyユーザに自分のuser_idが入っているマイクロポスト。
-
1
def self.from_users_followed_by(user)
-
75
followed_user_ids = "SELECT followed_id FROM relationships
-
WHERE follower_id = :user_id"
-
75
where("user_id IN (#{followed_user_ids}) OR user_id = :user_id OR in_reply_to = :user_id",
-
user_id: user.id)
-
end
-
end
-
1
class Relationship < ActiveRecord::Base
-
1
belongs_to :follower, class_name: "User"
-
1
belongs_to :followed, class_name: "User"
-
1
validates :follower_id, presence: true
-
1
validates :followed_id, presence: true
-
end
-
1
class User < ActiveRecord::Base
-
1
has_many :microposts, dependent: :destroy
-
1
has_many :replies, foreign_key: "in_reply_to", class_name: "Micropost", dependent: :destroy
-
1
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
-
1
has_many :followed_users, through: :relationships, source: :followed
-
#follow/ed_idを主キーとして渡すことでreverse_relationshipsをシミュレートするための1行
-
1
has_many :reverse_relationships, foreign_key: "followed_id",
-
class_name: "Relationship",
-
dependent: :destroy
-
1
has_many :followers, through: :reverse_relationships, source: :follower
-
-
1
has_many :messages, foreign_key: "sender_id", class_name: "Message", dependent: :destroy
-
1
has_many :recipient_messages, foreign_key: "reciptient_id", class_name: "Message", dependent: :destroy
-
-
300
before_save { self.email = email.downcase }
-
1
before_create :create_remember_token
-
-
1
VALID_NAMENICK_REGEX = /[^\w]/
-
1
VALID_EMAIL_REGEX = /\A[\w+-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
-
-
#presence 値が空ではないか case_sensitive 大文字小文字を区別するか
-
1
validates :name, presence: true, length: { maximum: 50 }
-
1
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
-
1
validates :nickname, presence: true, length: { maximum: 15 }, format: { without: VALID_NAMENICK_REGEX ,message: "Nick Nameは、英数字と'_'(アンダーバー)のみ使えます"}, uniqueness: { case_sensitive: false }
-
1
validates :password, length: { minimum: 6 }
-
-
#以下設定により、認証メソッドなどが使用できる
-
1
has_secure_password
-
-
1
def User.new_remember_token
-
271
SecureRandom.urlsafe_base64
-
end
-
-
1
def User.encrypt(token)
-
2015
Digest::SHA1.hexdigest(token.to_s)
-
end
-
-
1
def feed
-
#返信機能のため以下コメントにして、スコープ追加
-
#Micropost.from_users_followed_by(self)
-
75
Micropost.including_replies(self)
-
end
-
-
# following?メソッドはother_userという1人のユーザーを引数にとり、
-
# フォローする相手のユーザーがデータベース上に存在するかどうかをチェックします。
-
# follow!メソッドは、relationships関連付けを経由してcreate!を呼び出す
-
1
def following?(other_user)
-
14
relationships.find_by(followed_id: other_user.id)
-
end
-
-
1
def follow!(other_user)
-
27
relationships.create!(followed_id: other_user.id)
-
end
-
-
1
def unfollow!(other_user)
-
7
relationships.find_by(followed_id: other_user.id).destroy
-
end
-
-
1
private
-
-
1
def create_remember_token
-
203
self.remember_token = User.encrypt(User.new_remember_token)
-
end
-
end
-
# require 'rails_helper'
-
# include ApplicationHelper
-
# # RSpec.describe "StaticPages", type: :request do
-
# # describe "GET /static_pages" do
-
# # it "works! (now write some real specs)" do
-
# # visit '/static_pages/home'
-
# # expect(response).to have_content('Sample App')
-
# # end
-
# # end
-
# # end
-
#
-
# feature "Static pages" do
-
# subject { page }
-
#
-
# shared_examples_for "all static pages" do
-
# it{should have_content(heading)}
-
# it{should have_title(full_title(page_title))}
-
# end
-
#
-
# feature "Home page" do
-
# before { visit root_path }
-
# let(:heading) {'Sample App'}
-
# let(:page_title) {''}
-
#
-
# it { should_not have_title('Home') }
-
# it_should_behave_like "all static pages"
-
# end
-
#
-
# feature "Help page" do
-
# before { visit help_path }
-
# let(:heading) {'Help'}
-
# let(:page_title) {'Help'}
-
#
-
# it_should_behave_like "all static pages"
-
# end
-
#
-
# feature "About page" do
-
# before { visit about_path }
-
# let(:heading) {'About Us'}
-
# let(:page_title) {'About Us'}
-
#
-
# it_should_behave_like "all static pages"
-
# end
-
#
-
#
-
# feature "Contact page" do
-
# before { visit contact_path }
-
# let(:heading) {'Contact'}
-
# let(:page_title) {'Contact'}
-
#
-
# it_should_behave_like "all static pages"
-
# end
-
#
-
# it "should have the right links on the layout" do
-
# visit root_path
-
# click_link "About"
-
# expect(page).to have_title(full_title('About Us'))
-
# click_link "Help"
-
# expect(page).to have_title(full_title('Help'))
-
# click_link "Contact"
-
# expect(page).to have_title(full_title('Contact'))
-
# click_link "Home"
-
# click_link "Sign up now!"
-
# expect(page).to have_title(full_title('Sign up'))
-
# click_link "sample app"
-
# expect(page).to have_title(full_title(''))
-
# end
-
#
-
# end
-
# require 'rails_helper'
-
# include ApplicationHelper
-
#
-
# feature "User pages" do
-
# subject { page }
-
#
-
# feature "signup page" do
-
# before { visit signup_path }
-
#
-
# it { should have_content('Sign up') }
-
# it { should have_title(full_title('Sign up')) }
-
# end
-
#
-
# feature "profile page" do
-
# let(:user) { FactoryGirl.create(:user) }
-
# before { visit user_path(user) }
-
#
-
# it { should have_content(user.name) }
-
# it { should have_title(user.name) }
-
# end
-
# end
-
#Rspecの3.0以上では、spec_helperではなく、rails_helperでrequireしないと読み込まれない
-
1
require 'rails_helper'
-
-
1
describe ApplicationHelper do
-
-
1
describe "full_title" do
-
1
it "should include the page title" do
-
1
expect(full_title("foo")).to match(/foo/)
-
end
-
-
1
it "should include the base title" do
-
1
expect(full_title("foo")).to match(/^Ruby on Rails Tutorial Sample App/)
-
end
-
-
1
it "should not include a bar for the home page" do
-
1
expect(full_title("")).not_to match(/\|/)
-
end
-
end
-
end
-
1
require 'rails_helper'
-
1
require 'rails_helper'
-
-
-
1
describe Message do
-
-
11
let(:user) { FactoryGirl.create(:user) }
-
11
let(:other_user) { FactoryGirl.create(:user) }
-
1
before do
-
10
user.save
-
10
other_user.save
-
10
@message = Message.new(content: "test",sender_id: user.id, reciptient_id: other_user.id)
-
end
-
-
11
subject { @message }
-
-
2
it { should respond_to(:content) }
-
2
it { should respond_to(:sender_id) }
-
2
it { should respond_to(:reciptient_id) }
-
-
2
it { should respond_to(:sender_user) }
-
2
it { should respond_to(:reciptient_user) }
-
-
2
it { should be_valid }
-
-
1
describe "when content is not present" do
-
2
before { @message.content = nil }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when sender_id is not present" do
-
2
before { @message.sender_id = nil }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when reciptient_id is not present" do
-
2
before { @message.reciptient_id = nil }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "with content that is too long" do
-
2
before { @message.content = "a" * 141 }
-
2
it { should_not be_valid }
-
end
-
-
end
-
1
require 'rails_helper'
-
-
-
1
describe Micropost do
-
9
let(:user) { FactoryGirl.create(:user) }
-
9
before { @micropost = user.microposts.build(content: "Lorem ipsum") }
-
-
9
subject { @micropost }
-
-
2
it { should respond_to(:content) }
-
2
it { should respond_to(:user_id) }
-
2
it { should respond_to(:user) }
-
2
its(:user) { should eq user }
-
-
2
it { should be_valid }
-
-
1
describe "when user_id is not present" do
-
2
before { @micropost.user_id = nil }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when content is not present" do
-
2
before { @micropost.content = nil }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "with content that is too long" do
-
2
before { @micropost.content = "a" * 141 }
-
2
it { should_not be_valid }
-
end
-
end
-
1
require 'rails_helper'
-
-
1
describe Relationship do
-
-
8
let(:follower) { FactoryGirl.create(:user) }
-
8
let(:followed) { FactoryGirl.create(:user) }
-
8
let(:relationship) { follower.relationships.build(followed_id: followed.id) }
-
-
8
subject { relationship }
-
-
2
it { should be_valid }
-
-
1
describe "follower methods" do
-
2
it { should respond_to(:follower) }
-
2
it { should respond_to(:followed) }
-
2
its(:follower) { should eq follower }
-
2
its(:followed) { should eq followed }
-
end
-
-
1
describe "when followed id is not present" do
-
2
before { relationship.followed_id = nil }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when follower id is not present" do
-
2
before { relationship.follower_id = nil }
-
2
it { should_not be_valid }
-
end
-
end
-
1
require 'rails_helper'
-
-
1
describe User do
-
-
1
before do
-
49
@user = User.new(name: "Example User", email: "user@example.com", nickname: "ExUser",
-
password: "foobar", password_confirmation: "foobar")
-
end
-
-
43
subject { @user }
-
-
2
it { should respond_to(:name) }
-
2
it { should respond_to(:email) }
-
2
it { should respond_to(:nickname) }
-
2
it { should respond_to(:password_digest) }
-
2
it { should respond_to(:password) }
-
2
it { should respond_to(:password_confirmation) }
-
2
it { should respond_to(:remember_token) }
-
2
it { should respond_to(:authenticate) }
-
2
it { should respond_to(:admin) }
-
2
it { should respond_to(:microposts) }
-
2
it { should respond_to(:feed) }
-
2
it { should respond_to(:relationships) }
-
2
it { should respond_to(:followed_users) }
-
2
it { should respond_to(:reverse_relationships) }
-
2
it { should respond_to(:followers) }
-
#フォローリレーションシップの存在確認メソッドのテスト
-
2
it { should respond_to(:following?) }
-
2
it { should respond_to(:follow!) }
-
-
2
it { should be_valid }
-
2
it { should_not be_admin }
-
-
1
describe "with admin attribute set to 'true'" do
-
1
before do
-
1
@user.save!
-
1
@user.toggle!(:admin)
-
end
-
-
2
it { should be_admin }
-
end
-
-
1
describe "when name is not present" do
-
2
before { @user.name = " " }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when name is too long" do
-
2
before { @user.name = "a" * 51 }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when email format is invalid" do
-
1
it "should be invalid" do
-
1
addresses = %w[user@foo,com user_at_foo.org example.user@foo.
-
foo@bar_baz.com foo@bar+baz.com]
-
1
addresses.each do |invalid_address|
-
5
@user.email = invalid_address
-
5
expect(@user).not_to be_valid
-
end
-
end
-
end
-
-
1
describe "when email format is valid" do
-
1
it "should be valid" do
-
1
addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
-
1
addresses.each do |valid_address|
-
4
@user.email = valid_address
-
4
expect(@user).to be_valid
-
end
-
end
-
end
-
-
1
describe "when email address is already taken" do
-
1
before do
-
#同様のオブジェクトを作成
-
1
user_with_same_email = @user.dup
-
#大文字のメルアドを作成
-
1
user_with_same_email.email = @user.email.upcase
-
1
user_with_same_email.save
-
end
-
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when email doesn't match validation" do
-
2
let(:missmatch_case_email) { "foo@bar..com" }
-
-
1
it "should_not be_valid" do
-
1
@user.email = missmatch_case_email
-
1
@user.save
-
1
should_not be_valid
-
end
-
end
-
-
1
describe "when password is not present" do
-
1
before do
-
1
@user = User.new(name: "Example User", email: "user@example.com", nickname: "ExUser",
-
password: " ", password_confirmation: " ")
-
end
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when password doesn't match confirmation" do
-
2
before { @user.password_confirmation = "missmatch" }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when nickname is not present" do
-
2
before { @user.nickname = " " }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when nickname is too long" do
-
2
before { @user.nickname = "a" * 16 }
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when nickname is too long" do
-
2
before { @user.nickname = "a a"}
-
2
it { should_not be_valid }
-
end
-
-
1
describe "when nickname is too long" do
-
2
before { @user.nickname = "@aa"}
-
2
it { should_not be_valid }
-
end
-
-
1
describe "with a password that's too short" do
-
2
before { @user.password = @user.password_confirmation = "a" * 5 }
-
2
it { should be_invalid }
-
end
-
-
1
describe "return value of authenticate method" do
-
4
before { @user.save }
-
4
let(:found_user) {User.find_by(email: @user.email) }
-
-
1
describe "with valid password" do
-
2
it { should eq found_user.authenticate(@user.password) }
-
end
-
-
1
describe "with invalid password" do
-
3
let(:user_for_invalid_password) { found_user.authenticate("invalid") }
-
-
2
it { should_not eq user_for_invalid_password }
-
#rspec 3.0以上では、be_true は be_truthy be_falseはbe_falseyに変更されている
-
2
specify { expect(user_for_invalid_password).to be_falsey }
-
end
-
end
-
-
1
describe "email address with mixed case" do
-
2
let(:mixed_case_email) { "Foo@ExAMPle.CoM" }
-
-
1
it "should be saved as all lower-case" do
-
1
@user.email = mixed_case_email
-
1
@user.save
-
1
expect(@user.reload.email).to eq mixed_case_email.downcase
-
end
-
end
-
-
1
describe "remember token" do
-
2
before { @user.save }
-
#itsメソッドは、引数として与えられたものremember_tokenに対して評価します。
-
#つまり、it { expect(@user.remember_token).not_to be_blank }
-
#gem 'rspec-its'を入れないといけない
-
2
its(:remember_token) { should_not be_blank }
-
end
-
-
1
describe "micropost associations" do
-
7
before { @user.save }
-
-
1
let!(:older_micropost) do
-
6
FactoryGirl.create(:micropost, user: @user, created_at: 1.day.ago)
-
end
-
1
let!(:newer_micropost) do
-
6
FactoryGirl.create(:micropost, user: @user, created_at: 1.hour.ago)
-
end
-
-
1
it "should have the right microposts in the right order" do
-
1
expect(@user.microposts.to_a).to eq [newer_micropost, older_micropost]
-
end
-
-
1
it "should destroy associated microposts" do
-
1
microposts = @user.microposts.to_a
-
1
@user.destroy
-
1
expect(microposts).not_to be_empty
-
1
microposts.each do |micropost|
-
2
expect(Micropost.where(id: micropost.id)).to be_empty
-
end
-
end
-
-
1
describe "status" do
-
1
let(:unfollowed_post) do
-
1
FactoryGirl.create(:micropost, user: FactoryGirl.create(:user))
-
end
-
5
let(:followed_user) { FactoryGirl.create(:user) }
-
-
1
before do
-
4
@user.follow!(followed_user)
-
16
3.times { followed_user.microposts.create!(content: "Lorem ipsum") }
-
end
-
-
2
its(:feed) { should include(newer_micropost) }
-
2
its(:feed) { should include(older_micropost) }
-
2
its(:feed) { should_not include(unfollowed_post) }
-
1
its(:feed) do
-
1
followed_user.microposts.each do |micropost|
-
3
should include(micropost)
-
end
-
end
-
end
-
end
-
-
1
describe "following" do
-
6
let(:other_user) { FactoryGirl.create(:user) }
-
1
before do
-
5
@user.save
-
#other_user がDBにいるかチェック
-
5
@user.follow!(other_user)
-
end
-
-
2
it { should be_following(other_user) }
-
2
its(:followed_users) { should include(other_user) }
-
-
1
describe "followed user" do
-
2
subject { other_user }
-
2
its(:followers) { should include(@user) }
-
end
-
-
1
describe "and unfollowing" do
-
3
before { @user.unfollow!(other_user) }
-
-
2
it { should_not be_following(other_user) }
-
2
its(:followed_users) { should_not include(other_user) }
-
end
-
end
-
end
-
1
require 'rails_helper'
-
-
1
describe "Authentication" do
-
18
subject { page }
-
-
1
describe "signin page" do
-
11
before { visit signin_path }
-
-
#サインインしていない場合、Profileなどのリンクがないことのテスト
-
1
describe "with invalid information" do
-
2
it { should_not have_link('Users', href: users_path) }
-
2
it { should_not have_link('Profile') }
-
2
it { should_not have_link('Sign out', href: signout_path) }
-
end
-
-
1
describe "with invalid information" do
-
8
let(:user) {FactoryGirl.create(:user) }
-
# #上記をutilitiesでまとめたマッチャー
-
8
before { sign_in user }
-
-
#サインイン後のパスは、homeになるテスト
-
2
it { should have_link('view my profile') }
-
-
-
#index用のテストリンク
-
2
it { should have_link('Users', href: users_path) }
-
2
it { should have_link('Profile', href: user_path(user)) }
-
2
it { should have_link('Sign out', href: signout_path) }
-
2
it { should_not have_link('Sign in', href: signin_path) }
-
#ユーザ編集ページもログイン後にあるかどうかのテストを行う
-
2
it { should have_link('Setting', href: edit_user_path(user)) }
-
-
#サインアウト時にSign inリンクが有るか
-
1
describe "followed by signout" do
-
2
before { click_link "Sign out" }
-
-
2
it { should have_link('Sign in') }
-
end
-
-
end
-
end
-
-
1
describe "signin" do
-
4
before { visit signin_path }
-
-
1
describe "with invalid information" do
-
4
before { click_button "Sign in" }
-
-
2
it { should have_title('Sign in') }
-
#it { should have_selector('div.alert.alert-error', text: 'Invalid') }
-
#上記をutilitiesでカスタムマッチャー
-
2
it { should have_error_message('Invalid') }
-
-
1
describe "after visiting antoher page" do
-
2
before { click_link "Home" }
-
-
2
it { should_not have_selector('div.alert.alert-error') }
-
end
-
end
-
end
-
-
1
describe "authorization" do
-
1
describe "for non-signed-in users" do
-
6
let(:user) { FactoryGirl.create(:user) }
-
-
1
describe "when attempting to visit a protected page" do
-
1
before do
-
1
visit edit_user_path(user)
-
1
sign_in user
-
end
-
-
1
describe "after signing in" do
-
1
it "should render the desired protected page" do
-
1
expect(page).to have_title('Edit user')
-
end
-
end
-
end
-
-
1
describe "in the Microposts controller" do
-
-
1
describe "submitting to the create action" do
-
2
before { post microposts_path }
-
2
specify { expect(response).to redirect_to(signin_path) }
-
end
-
-
1
describe "submitting to the destroy action" do
-
2
before { delete micropost_path(FactoryGirl.create(:micropost)) }
-
2
specify { expect(response).to redirect_to(signin_path) }
-
end
-
end
-
-
1
describe "in the Users controller" do
-
-
1
describe "visiting the edit page" do
-
2
before { visit edit_user_path(user) }
-
-
2
it { should have_title('Sign in') }
-
end
-
-
1
describe "submitting to the update action" do
-
2
before { patch user_path(user) }
-
2
specify { expect(response).to redirect_to(signin_path) }
-
end
-
-
1
describe "visiting the user index" do
-
2
before { visit users_path }
-
2
it { should have_title('Sign in') }
-
end
-
-
1
describe "visiting the following page" do
-
2
before { visit following_user_path(user) }
-
2
it { should have_title('Sign in') }
-
end
-
-
1
describe "visiting the followers page" do
-
2
before { visit followers_user_path(user) }
-
2
it { should have_title('Sign in') }
-
end
-
end
-
-
1
describe "as non-admin user" do
-
2
let(:user) { FactoryGirl.create(:user) }
-
2
let(:non_admin) { FactoryGirl.create(:user) }
-
-
2
before { sign_in non_admin, no_capybara: true }
-
-
1
describe "submitting a DELETE request to the users#destory action" do
-
2
before { delete user_path(user) }
-
2
specify { expect(response).to redirect_to(root_path) }
-
end
-
end
-
-
1
describe "in the Relationships controller" do
-
1
describe "submitting to the create action" do
-
2
before { post relationships_path }
-
2
specify { expect(response).to redirect_to(signin_path) }
-
end
-
-
1
describe "submitting to the destroy action" do
-
2
before { delete relationship_path(1) }
-
2
specify { expect(response).to redirect_to(signin_path) }
-
end
-
end
-
end
-
-
1
describe "as wrong user" do
-
4
let(:user) { FactoryGirl.create(:user) }
-
4
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }
-
4
before { sign_in user, no_capybara: true }
-
-
1
describe "submitting a GET request to the Users#edit action" do
-
3
before { get edit_user_path(wrong_user) }
-
2
specify { expect(response.body).not_to match(full_title('Edit user')) }
-
2
specify { expect(response).to redirect_to(root_url) }
-
end
-
-
1
describe "submitting a PATCH request to the Users#update action" do
-
2
before { patch user_path(wrong_user) }
-
2
specify { expect(response).to redirect_to(root_path) }
-
end
-
end
-
end
-
end
-
1
require 'rails_helper'
-
-
1
describe "Message pages" do
-
-
3
subject { page }
-
-
5
let(:user) { FactoryGirl.create(:user) }
-
5
let(:other_user) { FactoryGirl.create(:user) }
-
1
before do
-
4
sign_in user
-
end
-
-
1
describe "nothing messages" do
-
2
before { visit message_path(other_user) }
-
-
2
it { should have_content("メッセージがないか、相手がいません") }
-
end
-
-
1
describe "message creation" do
-
1
before do
-
3
@message = Message.new(content: "test",sender_id: user.id, reciptient_id: other_user.id)
-
3
@message.save
-
3
visit message_path(other_user)
-
end
-
-
1
describe "with invalid information" do
-
-
1
it "should not create a message" do
-
2
expect { click_button "Post" }.not_to change(Message, :count)
-
end
-
-
1
describe "error messages" do
-
2
before { click_button "Post" }
-
2
it { should have_content('error') }
-
end
-
end
-
-
1
describe "with valid information" do
-
-
2
before { fill_in 'message_content', with: "Lorem ipsum" }
-
1
it "should create a message" do
-
2
expect { click_button "Post" }.to change(Message, :count).by(1)
-
end
-
end
-
-
end
-
end
-
1
require 'rails_helper'
-
-
1
describe "Micropost pages" do
-
-
3
subject { page }
-
-
6
let(:user) { FactoryGirl.create(:user) }
-
6
before { sign_in user }
-
-
1
describe "micropost creation" do
-
5
before { visit root_path }
-
-
1
describe "with invalid information" do
-
-
1
it "should not create a micropost" do
-
2
expect { click_button "Post" }.not_to change(Micropost, :count)
-
end
-
-
1
describe "error messages" do
-
2
before { click_button "Post" }
-
2
it { should have_content('error') }
-
end
-
end
-
-
1
describe "with valid information" do
-
-
2
before { fill_in 'micropost_content', with: "Lorem ipsum" }
-
1
it "should create a micropost" do
-
2
expect { click_button "Post" }.to change(Micropost, :count).by(1)
-
end
-
end
-
-
1
describe "reply micropost" do
-
2
let(:reply_user) { FactoryGirl.create(:user) }
-
1
before do
-
1
user.save
-
1
reply_user.save
-
1
fill_in 'micropost_content', with: "@#{reply_user.nickname} test"
-
1
click_button "Post"
-
1
sign_in reply_user
-
1
visit root_path
-
end
-
-
2
it { should have_content("@#{reply_user.nickname} test") }
-
end
-
end
-
-
1
describe "micropost destruction" do
-
2
before { FactoryGirl.create(:micropost, user: user) }
-
-
1
describe "as correct user" do
-
2
before { visit root_path }
-
-
1
it "should delete a micropost" do
-
2
expect { click_link "delete" }.to change(Micropost, :count).by(-1)
-
end
-
end
-
end
-
end
-
1
require 'rails_helper'
-
1
describe "Static pages" do
-
12
subject { page }
-
-
1
shared_examples_for "all static pages" do
-
8
it{should have_content(heading)}
-
8
it{should have_title(full_title(page_title))}
-
end
-
-
1
describe "Home page" do
-
7
before { visit root_path }
-
2
let(:heading) {'Sample App'}
-
2
let(:page_title) {''}
-
-
2
it { should_not have_title('Home') }
-
1
it_should_behave_like "all static pages"
-
-
-
1
describe "for signed-in users" do
-
4
let(:user) { FactoryGirl.create(:user) }
-
1
before do
-
3
FactoryGirl.create(:micropost, user: user, content: "Lorem ipsum")
-
3
FactoryGirl.create(:micropost, user: user, content: "Dolor sit amet")
-
3
sign_in user
-
3
visit root_path
-
end
-
-
1
it "should render the user's feed" do
-
1
user.feed.each do |item|
-
2
expect(page).to have_selector("li##{item.id}", text: item.content)
-
end
-
end
-
-
#フォローしているユーザーとフォロワーのカウントがページに表示され、それぞれに正しいURLが設定されている
-
1
describe "follower/following counts" do
-
3
let(:other_user) { FactoryGirl.create(:user) }
-
1
before do
-
2
other_user.follow!(user)
-
2
visit root_path
-
end
-
-
2
it { should have_link("0 following", href: following_user_path(user)) }
-
2
it { should have_link("1 followers", href: followers_user_path(user)) }
-
end
-
end
-
end
-
-
1
describe "Help page" do
-
3
before { visit help_path }
-
2
let(:heading) {'Help'}
-
2
let(:page_title) {'Help'}
-
-
1
it_should_behave_like "all static pages"
-
end
-
-
1
describe "About page" do
-
3
before { visit about_path }
-
2
let(:heading) {'About Us'}
-
2
let(:page_title) {'About Us'}
-
-
1
it_should_behave_like "all static pages"
-
end
-
-
-
1
describe "Contact page" do
-
3
before { visit contact_path }
-
2
let(:heading) {'Contact'}
-
2
let(:page_title) {'Contact'}
-
-
1
it_should_behave_like "all static pages"
-
end
-
-
1
it "should have the right links on the layout" do
-
1
visit root_path
-
1
click_link "About"
-
1
expect(page).to have_title(full_title('About Us'))
-
1
click_link "Help"
-
1
expect(page).to have_title(full_title('Help'))
-
1
click_link "Contact"
-
1
expect(page).to have_title(full_title('Contact'))
-
1
click_link "Home"
-
1
click_link "Sign up now!"
-
1
expect(page).to have_title(full_title('Sign up'))
-
1
click_link "sample app"
-
1
expect(page).to have_title(full_title(''))
-
end
-
-
end
-
1
require 'rails_helper'
-
-
1
describe "User pages" do
-
36
subject { page }
-
-
1
describe "index" do
-
9
let(:user) { FactoryGirl.create(:user) }
-
1
before(:each) do
-
8
sign_in user
-
8
visit users_path
-
end
-
-
2
it { should have_title('All users') }
-
2
it { should have_content('All users') }
-
-
1
describe "pagination" do
-
32
before(:all) {30.times { FactoryGirl.create(:user) } }
-
2
after(:all) {User.delete_all}
-
-
2
it {should have_selector('div.pagination') }
-
-
1
it "should list each user" do
-
#paginationでとることによりそれぞれのページでの全員を持ってくることができる
-
1
User.paginate(page: 1).each do |user|
-
30
expect(page).to have_selector('li', text: user.name)
-
end
-
end
-
end
-
-
#indexのリストの中にdeleteがあるためここでテストする
-
1
describe "delete links" do
-
-
#自分自身の削除リンクがないことを確認
-
2
it { should_not have_link('delete') }
-
-
1
describe "as an admin user" do
-
4
let(:admin) { FactoryGirl.create(:admin) }
-
1
before do
-
3
sign_in admin
-
3
visit users_path
-
end
-
-
2
it { should have_link('delete', href: user_path(User.first)) }
-
1
it "should be able to delete another user" do
-
1
expect do
-
#どのdeleteリンクをクリックしてもよいという指令
-
1
click_link('delete', match: :first)
-
end.to change(User, :count).by(-1)
-
end
-
2
it { should_not have_link('delete', href: user_path(admin)) }
-
end
-
end
-
end
-
-
-
1
describe "signup" do
-
-
12
before { visit signup_path }
-
-
12
let(:submit) { "Create my account" }
-
-
1
describe "with invalid information" do
-
1
it "should not create a user" do
-
2
expect { click_button submit }.not_to change(User, :count)
-
end
-
end
-
-
1
describe "with valid information" do
-
1
before do
-
5
fill_in "Name", with: "Example User"
-
5
fill_in "Email", with: "user@example.com"
-
5
fill_in "Nick Name(登録後変更できません)", with: "ExUser"
-
5
fill_in "Password", with: "foobar"
-
5
fill_in "Confirm Password", with: "foobar"
-
end
-
-
1
it "should create a user" do
-
#カウントが1つ増えることを確認する
-
2
expect { click_button submit }.to change(User, :count).by(1)
-
end
-
-
1
describe "after saving the user" do
-
5
before { click_button submit }
-
2
let(:user) { User.find_by(email: 'user@example.com') }
-
-
#新規ユーザ登録後にユーザがサインインしたことをテストする
-
2
it { should have_link('Sign out') }
-
2
it { should have_title(user.name) }
-
#特定のcssクラスに属する特定のHTMLタグが存在しているかどうかをテストします
-
#マッチャーにまとめた it { should have_selector('div.alert.alert-success', text: 'Welcome') }
-
2
it { should have_success_message('Welcome')}
-
-
1
describe "redirect root url from signup_path" do
-
2
before { visit signup_path }
-
-
#サインインした場合、homeviewにリダイレクトしてるかテスト
-
2
it { should have_link('Sign out') }
-
end
-
end
-
end
-
-
1
describe "after submission" do
-
6
before { click_button submit }
-
-
2
it { should have_title('Sign up') }
-
2
it { should have_content('error') }
-
2
it { should have_content("can't be blank") }
-
2
it { should have_content('too short') }
-
2
it { should have_content('is invalid') }
-
end
-
end
-
-
-
1
describe "edit" do
-
11
let(:user) {FactoryGirl.create(:user) }
-
1
before do
-
10
sign_in user
-
10
visit edit_user_path(user)
-
end
-
-
1
describe "page" do
-
2
it { should have_content("Update your profile") }
-
2
it { should have_title("Edit user") }
-
2
it { should have_link('change', href: 'http://gravatar.com/emails') }
-
end
-
-
1
describe "with invalid information" do
-
2
before { click_button "Save changes" }
-
-
2
it { should have_content('error') }
-
end
-
-
1
describe "with valid information" do
-
6
let(:new_name) { "New_Name" }
-
6
let(:new_email) { "new@example.com" }
-
1
before do
-
5
fill_in "Name", with: new_name
-
5
fill_in "Email", with: new_email
-
5
fill_in "Password", with: user.password
-
5
fill_in "Confirm Password", with: user.password
-
5
click_button "Save changes"
-
end
-
-
2
it { should have_title(new_name) }
-
2
it { should have_selector('div.alert.alert-success') }
-
2
it { should have_link('Sign out', href: signout_path) }
-
2
specify { expect(user.reload.name).to eq new_name }
-
2
specify { expect(user.reload.email).to eq new_email }
-
end
-
-
1
describe "forbidden attributes" do
-
1
let(:params) do
-
1
{ user: {admin: true, password: user.password, password_confirmation: user.password} }
-
end
-
1
before do
-
1
sign_in user, no_capabara: true
-
1
patch user_path(user), params
-
end
-
2
specify { expect(user.reload).not_to be_admin }
-
end
-
end
-
-
1
describe "profile page" do
-
12
let(:user) { FactoryGirl.create(:user) }
-
#letは遅延評価に対して let!はbeforeと同じタイミングで上から順番に評価されるっぽい
-
12
let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
-
12
let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }
-
-
12
before { visit user_path(user) }
-
-
2
it { should have_content(user.name) }
-
2
it { should have_title(user.name) }
-
-
1
describe "microposts" do
-
2
it { should have_content(m1.content) }
-
2
it { should have_content(m2.content) }
-
#countメソッドは直接DBを読みにいくいく
-
#特定user_idに対するmicropostsの数をDBに問い合わせる
-
2
it { should have_content(user.microposts.count) }
-
end
-
-
1
describe "follow/unfollow buttons" do
-
7
let(:other_user) { FactoryGirl.create(:user) }
-
7
before { sign_in user }
-
-
1
describe "following a user" do
-
4
before { visit user_path(other_user) }
-
-
1
it "should increment the followed user count" do
-
1
expect do
-
1
click_button "Follow"
-
end.to change(user.followed_users, :count).by(1)
-
end
-
-
1
it "should increment the other user's followers count" do
-
1
expect do
-
1
click_button "Follow"
-
end.to change(other_user.followers, :count).by(1)
-
end
-
-
1
describe "toggling the button" do
-
2
before { click_button "Follow" }
-
2
it { should have_xpath("//input[@value='Unfollow']") }
-
end
-
end
-
-
1
describe "unfollowing a user" do
-
1
before do
-
3
user.follow!(other_user)
-
3
visit user_path(other_user)
-
end
-
-
1
it "should decrement the followed user count" do
-
1
expect do
-
1
click_button "Unfollow"
-
end.to change(user.followed_users, :count).by(-1)
-
end
-
-
1
it "should decrement the other user's followers count" do
-
1
expect do
-
1
click_button "Unfollow"
-
end.to change(other_user.followers, :count).by(-1)
-
end
-
-
1
describe "toggling the button" do
-
2
before { click_button "Unfollow" }
-
2
it { should have_xpath("//input[@value='Follow']") }
-
end
-
end
-
end
-
end
-
-
1
describe "following/followers" do
-
7
let(:user) { FactoryGirl.create(:user) }
-
7
let(:other_user) { FactoryGirl.create(:user) }
-
7
before { user.follow!(other_user) }
-
-
1
describe "followed users" do
-
1
before do
-
3
sign_in user
-
3
visit following_user_path(user)
-
end
-
-
2
it { should have_title(full_title('Following')) }
-
2
it { should have_selector('h3', text: 'Following') }
-
2
it { should have_link(other_user.name, href: user_path(other_user)) }
-
end
-
-
1
describe "followers" do
-
1
before do
-
3
sign_in other_user
-
3
visit followers_user_path(other_user)
-
end
-
-
2
it { should have_title(full_title('Followers')) }
-
2
it { should have_selector('h3', text: 'Followers') }
-
2
it { should have_link(user.name, href: user_path(user)) }
-
end
-
end
-
end
-
1
include ApplicationHelper
-
-
1
def full_title(page_title)
-
270
base_title = "Ruby on Rails Tutorial Sample App"
-
270
if page_title.empty?
-
90
base_title
-
else
-
180
"#{base_title} | #{page_title}"
-
end
-
end
-
-
1
def sign_in(user, options={})
-
63
if options[:no_capybara]
-
# Capybaraを使用していない場合にもサインインする。
-
8
remember_token = User.new_remember_token
-
8
cookies[:remember_token] = remember_token
-
8
user.update_attribute(:remember_token, User.encrypt(remember_token))
-
else
-
55
visit signin_path
-
55
fill_in "Email", with: user.email
-
55
fill_in "Password", with: user.password
-
55
click_button "Sign in"
-
end
-
end
-
-
#カスタムマッチャー
-
1
RSpec::Matchers.define :have_error_message do |message|
-
1
match do |page|
-
1
expect(page).to have_selector('div.alert.alert-error', text: message)
-
end
-
end
-
-
1
RSpec::Matchers.define :have_success_message do |message|
-
1
match do |page|
-
1
expect(page).to have_selector('div.alert.alert-success', text: message)
-
end
-
end
-
-
1
module ActionController
-
1
module Testing
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
-
# TODO : Rewrite tests using controller.headers= to use Rack env
-
1
def headers=(new_headers)
-
@_response ||= ActionDispatch::Response.new
-
@_response.headers.replace(new_headers)
-
end
-
-
# Behavior specific to functional tests
-
1
module Functional # :nodoc:
-
1
def set_response!(request)
-
end
-
-
1
def recycle!
-
4
@_url_options = nil
-
4
self.response_body = nil
-
4
self.formats = nil
-
4
self.params = nil
-
end
-
end
-
-
1
module ClassMethods
-
1
def before_filters
-
_process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
1
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
-
1
def initialize(*)
-
2217
super
-
2217
encode!
-
end
-
-
1
def <<(value)
-
7798
return self if value.nil?
-
7219
super(value.to_s)
-
end
-
1
alias :append= :<<
-
-
1
def safe_concat(value)
-
10747
return self if value.nil?
-
10747
super(value.to_s)
-
end
-
1
alias :safe_append= :safe_concat
-
end
-
-
1
class StreamingBuffer #:nodoc:
-
1
def initialize(block)
-
@block = block
-
end
-
-
1
def <<(value)
-
value = value.to_s
-
value = ERB::Util.h(value) unless value.html_safe?
-
@block.call(value)
-
end
-
1
alias :concat :<<
-
1
alias :append= :<<
-
-
1
def safe_concat(value)
-
@block.call(value.to_s)
-
end
-
1
alias :safe_append= :safe_concat
-
-
1
def html_safe?
-
true
-
end
-
-
1
def html_safe
-
self
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
1
class OutputFlow #:nodoc:
-
1
attr_reader :content
-
-
1
def initialize
-
514
@content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
-
end
-
-
# Called by _layout_for to read stored values.
-
1
def get(key)
-
510
@content[key]
-
end
-
-
# Called by each renderer object to set the layout contents.
-
1
def set(key, value)
-
255
@content[key] = value
-
end
-
-
# Called by content_for
-
1
def append(key, value)
-
168
@content[key] << value
-
end
-
1
alias_method :append!, :append
-
-
end
-
-
1
class StreamingFlow < OutputFlow #:nodoc:
-
1
def initialize(view, fiber)
-
@view = view
-
@parent = nil
-
@child = view.output_buffer
-
@content = view.view_flow.content
-
@fiber = fiber
-
@root = Fiber.current.object_id
-
end
-
-
# Try to get an stored content. If the content
-
# is not available and we are inside the layout
-
# fiber, we set that we are waiting for the given
-
# key and yield.
-
1
def get(key)
-
return super if @content.key?(key)
-
-
if inside_fiber?
-
view = @view
-
-
begin
-
@waiting_for = key
-
view.output_buffer, @parent = @child, view.output_buffer
-
Fiber.yield
-
ensure
-
@waiting_for = nil
-
view.output_buffer, @child = @parent, view.output_buffer
-
end
-
end
-
-
super
-
end
-
-
# Appends the contents for the given key. This is called
-
# by provides and resumes back to the fiber if it is
-
# the key it is waiting for.
-
1
def append!(key, value)
-
super
-
@fiber.resume if @waiting_for == key
-
end
-
-
1
private
-
-
1
def inside_fiber?
-
Fiber.current.object_id != @root
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags # :nodoc:
-
1
class Base # :nodoc:
-
1
include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
-
1
include FormOptionsHelper
-
-
1
attr_reader :object
-
-
1
def initialize(object_name, method_name, template_object, options = {})
-
380
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
-
380
@template_object = template_object
-
-
380
@object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
-
380
@object = retrieve_object(options.delete(:object))
-
380
@options = options
-
380
@auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
-
end
-
-
# This is what child classes implement.
-
1
def render
-
raise NotImplementedError, "Subclasses must implement a render method"
-
end
-
-
1
private
-
-
1
def value(object)
-
object.send @method_name if object
-
end
-
-
1
def value_before_type_cast(object)
-
175
unless object.nil?
-
175
method_before_type_cast = @method_name + "_before_type_cast"
-
-
175
object.respond_to?(method_before_type_cast) ?
-
object.send(method_before_type_cast) :
-
value(object)
-
end
-
end
-
-
1
def retrieve_object(object)
-
380
if object
-
380
object
-
elsif @template_object.instance_variable_defined?("@#{@object_name}")
-
@template_object.instance_variable_get("@#{@object_name}")
-
end
-
rescue NameError
-
# As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
-
nil
-
end
-
-
1
def retrieve_autoindex(pre_match)
-
object = self.object || @template_object.instance_variable_get("@#{pre_match}")
-
if object && object.respond_to?(:to_param)
-
object.to_param
-
else
-
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
-
end
-
end
-
-
1
def add_default_name_and_id_for_value(tag_value, options)
-
143
if tag_value.nil?
-
143
add_default_name_and_id(options)
-
else
-
specified_id = options["id"]
-
add_default_name_and_id(options)
-
-
if specified_id.blank? && options["id"].present?
-
options["id"] += "_#{sanitized_value(tag_value)}"
-
end
-
end
-
end
-
-
1
def add_default_name_and_id(options)
-
380
if options.has_key?("index")
-
options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
-
options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
-
options.delete("index")
-
elsif defined?(@auto_index)
-
options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
-
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
-
else
-
760
options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
-
760
options["id"] = options.fetch("id"){ tag_id }
-
end
-
-
380
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
-
end
-
-
1
def tag_name(multiple = false)
-
380
"#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
-
end
-
-
1
def tag_name_with_index(index, multiple = false)
-
"#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
-
end
-
-
1
def tag_id
-
380
"#{sanitized_object_name}_#{sanitized_method_name}"
-
end
-
-
1
def tag_id_with_index(index)
-
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
-
end
-
-
1
def sanitized_object_name
-
380
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
-
end
-
-
1
def sanitized_method_name
-
760
@sanitized_method_name ||= @method_name.sub(/\?$/,"")
-
end
-
-
1
def sanitized_value(value)
-
value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
-
end
-
-
1
def select_content_tag(option_tags, options, html_options)
-
html_options = html_options.stringify_keys
-
add_default_name_and_id(html_options)
-
options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
-
value = options.fetch(:selected) { value(object) }
-
select = content_tag("select", add_options(option_tags, options, value), html_options)
-
-
if html_options["multiple"] && options.fetch(:include_hidden, true)
-
tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
-
else
-
select
-
end
-
end
-
-
1
def select_not_required?(html_options)
-
!html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1
-
end
-
-
1
def add_options(option_tags, options, value = nil)
-
if options[:include_blank]
-
option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
-
end
-
if value.blank? && options[:prompt]
-
option_tags = content_tag_string('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags
-
end
-
option_tags
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags # :nodoc:
-
1
class HiddenField < TextField # :nodoc:
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags # :nodoc:
-
1
class Label < Base # :nodoc:
-
1
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
-
143
options ||= {}
-
-
143
content_is_options = content_or_options.is_a?(Hash)
-
143
if content_is_options
-
options.merge! content_or_options
-
@content = nil
-
else
-
143
@content = content_or_options
-
end
-
-
143
super(object_name, method_name, template_object, options)
-
end
-
-
1
def render(&block)
-
143
options = @options.stringify_keys
-
143
tag_value = options.delete("value")
-
143
name_and_id = options.dup
-
-
143
if name_and_id["for"]
-
name_and_id["id"] = name_and_id["for"]
-
else
-
143
name_and_id.delete("id")
-
end
-
-
143
add_default_name_and_id_for_value(tag_value, name_and_id)
-
143
options.delete("index")
-
143
options.delete("namespace")
-
143
options["for"] = name_and_id["id"] unless options.key?("for")
-
-
143
if block_given?
-
content = @template_object.capture(&block)
-
else
-
143
content = if @content.blank?
-
62
@object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
-
62
method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
-
-
62
if object.respond_to?(:to_model)
-
62
key = object.class.model_name.i18n_key
-
62
i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
-
end
-
-
62
i18n_default ||= ""
-
62
I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
-
else
-
81
@content.to_s
-
end
-
-
content ||= if object && object.class.respond_to?(:human_attribute_name)
-
62
object.class.human_attribute_name(@method_name)
-
143
end
-
-
143
content ||= @method_name.humanize
-
end
-
-
143
label_tag(name_and_id["id"], content, options)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags # :nodoc:
-
1
class PasswordField < TextField # :nodoc:
-
1
def render
-
62
@options = {:value => nil}.merge!(@options)
-
62
super
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags # :nodoc:
-
1
class TextArea < Base # :nodoc:
-
1
def render
-
76
options = @options.stringify_keys
-
76
add_default_name_and_id(options)
-
-
76
if size = options.delete("size")
-
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
-
end
-
-
152
content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
1
module Helpers
-
1
module Tags # :nodoc:
-
1
class TextField < Base # :nodoc:
-
1
def render
-
161
options = @options.stringify_keys
-
161
options["size"] = options["maxlength"] unless options.key?("size")
-
161
options["type"] ||= field_type
-
260
options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
-
161
options["value"] &&= ERB::Util.html_escape(options["value"])
-
161
add_default_name_and_id(options)
-
161
tag("input", options)
-
end
-
-
1
class << self
-
1
def field_type
-
322
@field_type ||= self.name.split("::").last.sub("Field", "").downcase
-
end
-
end
-
-
1
private
-
-
1
def field_type
-
322
self.class.field_type
-
end
-
end
-
end
-
end
-
end
-
1
require 'thread_safe'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
1
module ActionView
-
# = Action View Lookup Context
-
#
-
# LookupContext is the object responsible to hold all information required to lookup
-
# templates, i.e. view paths and details. The LookupContext is also responsible to
-
# generate a key, given to view paths, used in the resolver cache lookup. Since
-
# this key is generated just once during the request, it speeds up all cache accesses.
-
1
class LookupContext #:nodoc:
-
1
attr_accessor :prefixes, :rendered_format
-
-
1
mattr_accessor :fallbacks
-
1
@@fallbacks = FallbackFileSystemResolver.instances
-
-
1
mattr_accessor :registered_details
-
1
self.registered_details = []
-
-
1
def self.register_detail(name, options = {}, &block)
-
3
self.registered_details << name
-
9
initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
-
-
3
Accessors.send :define_method, :"default_#{name}", &block
-
3
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{name}
-
@details.fetch(:#{name}, [])
-
end
-
-
def #{name}=(value)
-
value = value.present? ? Array(value) : default_#{name}
-
_set_detail(:#{name}, value) if value != @details[:#{name}]
-
end
-
-
remove_possible_method :initialize_details
-
def initialize_details(details)
-
#{initialize.join("\n")}
-
end
-
METHOD
-
end
-
-
# Holds accessors for the registered details.
-
1
module Accessors #:nodoc:
-
end
-
-
1
register_detail(:locale) do
-
356
locales = [I18n.locale]
-
356
locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
-
356
locales << I18n.default_locale
-
356
locales.uniq!
-
356
locales
-
end
-
365
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
-
357
register_detail(:handlers){ Template::Handlers.extensions }
-
-
1
class DetailsKey #:nodoc:
-
1
alias :eql? :equal?
-
1
alias :object_hash :hash
-
-
1
attr_reader :hash
-
1
@details_keys = ThreadSafe::Cache.new
-
-
1
def self.get(details)
-
263
if details[:formats]
-
263
details = details.dup
-
263
syms = Set.new Mime::SET.symbols
-
263
details[:formats] = details[:formats].select { |v|
-
267
syms.include? v
-
}
-
end
-
263
@details_keys[details] ||= new
-
end
-
-
1
def self.clear
-
@details_keys.clear
-
end
-
-
1
def initialize
-
3
@hash = object_hash
-
end
-
end
-
-
# Add caching behavior on top of Details.
-
1
module DetailsCache
-
1
attr_accessor :cache
-
-
# Calculate the details key. Remove the handlers from calculation to improve performance
-
# since the user cannot modify it explicitly.
-
1
def details_key #:nodoc:
-
2087
@details_key ||= DetailsKey.get(@details) if @cache
-
end
-
-
# Temporary skip passing the details_key forward.
-
1
def disable_cache
-
old_value, @cache = @cache, false
-
yield
-
ensure
-
@cache = old_value
-
end
-
-
1
protected
-
-
1
def _set_detail(key, value)
-
353
@details = @details.dup if @details_key
-
353
@details_key = nil
-
353
@details[key] = value
-
end
-
end
-
-
# Helpers related to template lookup using the lookup context information.
-
1
module ViewPaths
-
1
attr_reader :view_paths, :html_fallback_for_js
-
-
# Whenever setting view paths, makes a copy so we can manipulate then in
-
# instance objects as we wish.
-
1
def view_paths=(paths)
-
356
@view_paths = ActionView::PathSet.new(Array(paths))
-
end
-
-
1
def find(name, prefixes = [], partial = false, keys = [], options = {})
-
1565
@view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
1
alias :find_template :find
-
-
1
def find_all(name, prefixes = [], partial = false, keys = [], options = {})
-
522
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
-
1
def exists?(name, prefixes = [], partial = false, keys = [], options = {})
-
@view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
1
alias :template_exists? :exists?
-
-
# Add fallbacks to the view paths. Useful in cases you are rendering a :file.
-
1
def with_fallbacks
-
added_resolvers = 0
-
self.class.fallbacks.each do |resolver|
-
next if view_paths.include?(resolver)
-
view_paths.push(resolver)
-
added_resolvers += 1
-
end
-
yield
-
ensure
-
added_resolvers.times { view_paths.pop }
-
end
-
-
1
protected
-
-
1
def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
-
2087
name, prefixes = normalize_name(name, prefixes)
-
2087
details, details_key = detail_args_for(details_options)
-
2087
[name, prefixes, partial || false, details, details_key, keys]
-
end
-
-
# Compute details hash and key according to user options (e.g. passed from #render).
-
1
def detail_args_for(options)
-
2087
return @details, details_key if options.empty? # most common path.
-
user_details = @details.merge(options)
-
[user_details, DetailsKey.get(user_details)]
-
end
-
-
# Support legacy foo.erb names even though we now ignore .erb
-
# as well as incorrectly putting part of the path in the template
-
# name instead of the prefix.
-
1
def normalize_name(name, prefixes) #:nodoc:
-
2087
prefixes = prefixes.presence
-
2087
parts = name.to_s.split('/')
-
2087
parts.shift if parts.first.empty?
-
2087
name = parts.pop
-
-
2087
return name, prefixes || [""] if parts.empty?
-
-
1257
parts = parts.join('/')
-
1261
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
-
-
1257
return name, prefixes
-
end
-
end
-
-
1
include Accessors
-
1
include DetailsCache
-
1
include ViewPaths
-
-
1
def initialize(view_paths, details = {}, prefixes = [])
-
356
@details, @details_key = {}, nil
-
356
@skip_default_locale = false
-
356
@cache = true
-
356
@prefixes = prefixes
-
356
@rendered_format = nil
-
-
356
self.view_paths = view_paths
-
356
initialize_details(details)
-
end
-
-
# Override formats= to expand ["*/*"] values and automatically
-
# add :html as fallback to :js.
-
1
def formats=(values)
-
643
if values
-
639
values.concat(default_formats) if values.delete "*/*"
-
639
if values == [:js]
-
4
values << :html
-
4
@html_fallback_for_js = true
-
end
-
end
-
643
super(values)
-
end
-
-
# Do not use the default locale on template lookup.
-
1
def skip_default_locale!
-
@skip_default_locale = true
-
self.locale = nil
-
end
-
-
# Override locale to return a symbol instead of array.
-
1
def locale
-
@details[:locale].first
-
end
-
-
# Overload locale= to also set the I18n.locale. If the current I18n.config object responds
-
# to original_config, it means that it's has a copy of the original I18n configuration and it's
-
# acting as proxy, which we need to skip.
-
1
def locale=(value)
-
if value
-
config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
-
config.locale = value
-
end
-
-
super(@skip_default_locale ? I18n.locale : default_locale)
-
end
-
-
# A method which only uses the first format in the formats array for layout lookup.
-
1
def with_layout_format
-
259
if formats.size == 1
-
255
yield
-
else
-
4
old_formats = formats
-
4
_set_detail(:formats, formats[0,1])
-
-
4
begin
-
4
yield
-
ensure
-
4
_set_detail(:formats, old_formats)
-
end
-
end
-
end
-
end
-
end
-
1
module ActionView
-
# This class defines the interface for a renderer. Each class that
-
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
-
# render a specific type of object.
-
#
-
# The base +Renderer+ class uses its +render+ method to delegate to the
-
# renderers. These currently consist of
-
#
-
# PartialRenderer - Used for rendering partials
-
# TemplateRenderer - Used for rendering other types of templates
-
# StreamingTemplateRenderer - Used for streaming
-
#
-
# Whenever the +render+ method is called on the base +Renderer+ class, a new
-
# renderer object of the correct type is created, and the +render+ method on
-
# that new object is called in turn. This abstracts the setup and rendering
-
# into a separate classes for partials and templates.
-
1
class AbstractRenderer #:nodoc:
-
1
delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
-
-
1
def initialize(lookup_context)
-
1565
@lookup_context = lookup_context
-
end
-
-
1
def render
-
raise NotImplementedError
-
end
-
-
1
protected
-
-
1
def extract_details(options)
-
1565
@lookup_context.registered_details.each_with_object({}) do |key, details|
-
4695
next unless value = options[key]
-
details[key] = Array(value)
-
end
-
end
-
-
1
def instrument(name, options={})
-
3130
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
-
end
-
-
1
def prepend_formats(formats)
-
1565
formats = Array(formats)
-
1565
return if formats.empty? || @lookup_context.html_fallback_for_js
-
255
@lookup_context.formats = formats | @lookup_context.formats
-
end
-
end
-
end
-
1
require 'thread_safe'
-
-
1
module ActionView
-
# = Action View Partials
-
#
-
# There's also a convenience method for rendering sub templates within the current controller that depends on a
-
# single object (we call this kind of sub templates for partials). It relies on the fact that partials should
-
# follow the naming convention of being prefixed with an underscore -- as to separate them from regular
-
# templates that could be rendered on their own.
-
#
-
# In a template for Advertiser#account:
-
#
-
# <%= render partial: "account" %>
-
#
-
# This would render "advertiser/_account.html.erb".
-
#
-
# In another template for Advertiser#buy, we could have:
-
#
-
# <%= render partial: "account", locals: { account: @buyer } %>
-
#
-
# <% @advertisements.each do |ad| %>
-
# <%= render partial: "ad", locals: { ad: ad } %>
-
# <% end %>
-
#
-
# This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
-
# render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
-
#
-
# == The :as and :object options
-
#
-
# By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
-
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
-
#
-
# <%= render partial: "account", object: @buyer %>
-
#
-
# would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
-
# equivalent to:
-
#
-
# <%= render partial: "account", locals: { account: @buyer } %>
-
#
-
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
-
# wanted it to be +user+ instead of +account+ we'd do:
-
#
-
# <%= render partial: "account", object: @buyer, as: 'user' %>
-
#
-
# This is equivalent to
-
#
-
# <%= render partial: "account", locals: { user: @buyer } %>
-
#
-
# == Rendering a collection of partials
-
#
-
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
-
# render a sub template for each of the elements. This pattern has been implemented as a single method that
-
# accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
-
# example in "Using partials" can be rewritten with a single line:
-
#
-
# <%= render partial: "ad", collection: @advertisements %>
-
#
-
# This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
-
# iteration counter will automatically be made available to the template with a name of the form
-
# +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
-
#
-
# The <tt>:as</tt> option may be used when rendering partials.
-
#
-
# You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
-
# The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
-
#
-
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
-
#
-
# If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
-
# to specify a text which will displayed instead by using this form:
-
#
-
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
-
#
-
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
-
# just keep domain objects, like Active Records, in there.
-
#
-
# == Rendering shared partials
-
#
-
# Two controllers can share a set of partials and render them like this:
-
#
-
# <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
-
#
-
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
-
#
-
# == Rendering objects that respond to `to_partial_path`
-
#
-
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
-
# and pick the proper path by checking `to_partial_path` method.
-
#
-
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
-
# # <%= render partial: "accounts/account", locals: { account: @account} %>
-
# <%= render partial: @account %>
-
#
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
-
# # that's why we can replace:
-
# # <%= render partial: "posts/post", collection: @posts %>
-
# <%= render partial: @posts %>
-
#
-
# == Rendering the default case
-
#
-
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
-
# defaults of render to render partials. Examples:
-
#
-
# # Instead of <%= render partial: "account" %>
-
# <%= render "account" %>
-
#
-
# # Instead of <%= render partial: "account", locals: { account: @buyer } %>
-
# <%= render "account", account: @buyer %>
-
#
-
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
-
# # <%= render partial: "accounts/account", locals: { account: @account} %>
-
# <%= render @account %>
-
#
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
-
# # that's why we can replace:
-
# # <%= render partial: "posts/post", collection: @posts %>
-
# <%= render @posts %>
-
#
-
# == Rendering partials with layouts
-
#
-
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
-
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
-
# of users:
-
#
-
# <%# app/views/users/index.html.erb &>
-
# Here's the administrator:
-
# <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
-
#
-
# Here's the editor:
-
# <%= render partial: "user", layout: "editor", locals: { user: editor } %>
-
#
-
# <%# app/views/users/_user.html.erb &>
-
# Name: <%= user.name %>
-
#
-
# <%# app/views/users/_administrator.html.erb &>
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# <%= yield %>
-
# </div>
-
#
-
# <%# app/views/users/_editor.html.erb &>
-
# <div id="editor">
-
# Deadline: <%= user.deadline %>
-
# <%= yield %>
-
# </div>
-
#
-
# ...this will return:
-
#
-
# Here's the administrator:
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# Name: <%= user.name %>
-
# </div>
-
#
-
# Here's the editor:
-
# <div id="editor">
-
# Deadline: <%= user.deadline %>
-
# Name: <%= user.name %>
-
# </div>
-
#
-
# If a collection is given, the layout will be rendered once for each item in
-
# the collection. Just think these two snippets have the same output:
-
#
-
# <%# app/views/users/_user.html.erb %>
-
# Name: <%= user.name %>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <%# This does not use layouts %>
-
# <ul>
-
# <% users.each do |user| -%>
-
# <li>
-
# <%= render partial: "user", locals: { user: user } %>
-
# </li>
-
# <% end -%>
-
# </ul>
-
#
-
# <%# app/views/users/_li_layout.html.erb %>
-
# <li>
-
# <%= yield %>
-
# </li>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <ul>
-
# <%= render partial: "user", layout: "li_layout", collection: users %>
-
# </ul>
-
#
-
# Given two users whose names are Alice and Bob, these snippets return:
-
#
-
# <ul>
-
# <li>
-
# Name: Alice
-
# </li>
-
# <li>
-
# Name: Bob
-
# </li>
-
# </ul>
-
#
-
# The current object being rendered, as well as the object_counter, will be
-
# available as local variables inside the layout template under the same names
-
# as available in the partial.
-
#
-
# You can also apply a layout to a block within any template:
-
#
-
# <%# app/views/users/_chief.html.erb &>
-
# <%= render(layout: "administrator", locals: { user: chief }) do %>
-
# Title: <%= chief.title %>
-
# <% end %>
-
#
-
# ...this will return:
-
#
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# Title: <%= chief.name %>
-
# </div>
-
#
-
# As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
-
#
-
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
-
# an array to layout and treat it as an enumerable.
-
#
-
# <%# app/views/users/_user.html.erb &>
-
# <div class="user">
-
# Budget: $<%= user.budget %>
-
# <%= yield user %>
-
# </div>
-
#
-
# <%# app/views/users/index.html.erb &>
-
# <%= render layout: @users do |user| %>
-
# Title: <%= user.title %>
-
# <% end %>
-
#
-
# This will render the layout for each user and yield to the block, passing the user, each time.
-
#
-
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
-
#
-
# <%# app/views/users/_user.html.erb &>
-
# <div class="user">
-
# <%= yield user, :header %>
-
# Budget: $<%= user.budget %>
-
# <%= yield user, :footer %>
-
# </div>
-
#
-
# <%# app/views/users/index.html.erb &>
-
# <%= render layout: @users do |user, section| %>
-
# <%- case section when :header -%>
-
# Title: <%= user.title %>
-
# <%- when :footer -%>
-
# Deadline: <%= user.deadline %>
-
# <%- end -%>
-
# <% end %>
-
1
class PartialRenderer < AbstractRenderer
-
1
PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
-
1
h[k] = ThreadSafe::Cache.new
-
end
-
-
1
def initialize(*)
-
1306
super
-
1306
@context_prefix = @lookup_context.prefixes.first
-
end
-
-
1
def render(context, options, block)
-
1306
setup(context, options, block)
-
1306
identifier = (@template = find_partial) ? @template.identifier : @path
-
-
1306
@lookup_context.rendered_format ||= begin
-
if @template && @template.formats.present?
-
@template.formats.first
-
else
-
formats.first
-
end
-
end
-
-
1306
if @collection
-
49
instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
-
49
render_collection
-
end
-
else
-
1257
instrument(:partial, :identifier => identifier) do
-
1257
render_partial
-
end
-
end
-
end
-
-
1
def render_collection
-
49
return nil if @collection.blank?
-
-
49
if @options.key?(:spacer_template)
-
spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
-
end
-
-
49
result = @template ? collection_with_template : collection_without_template
-
49
result.join(spacer).html_safe
-
end
-
-
1
def render_partial
-
1257
view, locals, block = @view, @locals, @block
-
1257
object, as = @object, @variable
-
-
1257
if !block && (layout = @options[:layout])
-
layout = find_template(layout.to_s, @template_keys)
-
end
-
-
1257
object ||= locals[as]
-
1257
locals[as] = object
-
-
1257
content = @template.render(view, locals) do |*name|
-
view._layout_for(*name, &block)
-
end
-
-
1257
content = layout.render(view, locals){ content } if layout
-
1257
content
-
end
-
-
1
private
-
-
# Sets up instance variables needed for rendering a partial. This method
-
# finds the options and details and extracts them. The method also contains
-
# logic that handles the type of object passed in as the partial.
-
#
-
# If +options[:partial]+ is a string, then the +@path+ instance variable is
-
# set to that string. Otherwise, the +options[:partial]+ object must
-
# respond to +to_partial_path+ in order to setup the path.
-
1
def setup(context, options, block)
-
1306
@view = context
-
1306
partial = options[:partial]
-
-
1306
@options = options
-
1306
@locals = options[:locals] || {}
-
1306
@block = block
-
1306
@details = extract_details(options)
-
-
1306
prepend_formats(options[:formats])
-
-
1306
if String === partial
-
1276
@object = options[:object]
-
1276
@path = partial
-
1276
@collection = collection
-
else
-
30
@object = partial
-
-
30
if @collection = collection_from_object || collection
-
133
paths = @collection_data = @collection.map { |o| partial_path(o) }
-
30
@path = paths.uniq.size == 1 ? paths.first : nil
-
else
-
@path = partial_path
-
end
-
end
-
-
1306
if as = options[:as]
-
raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
-
as = as.to_sym
-
end
-
-
1306
if @path
-
1306
@variable, @variable_counter = retrieve_variable(@path, as)
-
1306
@template_keys = retrieve_template_keys
-
else
-
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
-
end
-
-
1306
self
-
end
-
-
1
def collection
-
1276
if @options.key?(:collection)
-
19
collection = @options[:collection]
-
19
collection.respond_to?(:to_ary) ? collection.to_ary : []
-
end
-
end
-
-
1
def collection_from_object
-
30
@object.to_ary if @object.respond_to?(:to_ary)
-
end
-
-
1
def find_partial
-
1306
if path = @path
-
1306
find_template(path, @template_keys)
-
end
-
end
-
-
1
def find_template(path, locals)
-
1306
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
-
1306
@lookup_context.find_template(path, prefixes, true, locals, @details)
-
end
-
-
1
def collection_with_template
-
49
view, locals, template = @view, @locals, @template
-
49
as, counter = @variable, @variable_counter
-
-
49
if layout = @options[:layout]
-
layout = find_template(layout, @template_keys)
-
end
-
-
49
index = -1
-
49
@collection.map do |object|
-
136
locals[as] = object
-
136
locals[counter] = (index += 1)
-
-
136
content = template.render(view, locals)
-
136
content = layout.render(view, locals) { content } if layout
-
136
content
-
end
-
end
-
-
1
def collection_without_template
-
view, locals, collection_data = @view, @locals, @collection_data
-
cache = {}
-
keys = @locals.keys
-
-
index = -1
-
@collection.map do |object|
-
index += 1
-
path, as, counter = collection_data[index]
-
-
locals[as] = object
-
locals[counter] = index
-
-
template = (cache[path] ||= find_template(path, keys + [as, counter]))
-
template.render(view, locals)
-
end
-
end
-
-
# Obtains the path to where the object's partial is located. If the object
-
# responds to +to_partial_path+, then +to_partial_path+ will be called and
-
# will provide the path. If the object does not respond to +to_partial_path+,
-
# then an +ArgumentError+ is raised.
-
#
-
# If +prefix_partial_path_with_controller_namespace+ is true, then this
-
# method will prefix the partial paths with a namespace.
-
1
def partial_path(object = @object)
-
103
object = object.to_model if object.respond_to?(:to_model)
-
-
103
path = if object.respond_to?(:to_partial_path)
-
103
object.to_partial_path
-
else
-
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
-
end
-
-
103
if @view.prefix_partial_path_with_controller_namespace
-
103
prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
-
else
-
path
-
end
-
end
-
-
1
def prefixed_partial_names
-
103
@prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
-
end
-
-
1
def merge_prefix_into_object_path(prefix, object_path)
-
2
if prefix.include?(?/) && object_path.include?(?/)
-
prefixes = []
-
prefix_array = File.dirname(prefix).split('/')
-
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
-
-
prefix_array.each_with_index do |dir, index|
-
break if dir == object_path_array[index]
-
prefixes << dir
-
end
-
-
(prefixes << object_path).join("/")
-
else
-
2
object_path
-
end
-
end
-
-
1
def retrieve_template_keys
-
1306
keys = @locals.keys
-
1306
keys << @variable if @object || @collection
-
1306
keys << @variable_counter if @collection
-
1306
keys
-
end
-
-
1
def retrieve_variable(path, as)
-
1306
variable = as || begin
-
1306
base = path[-1] == "/" ? "" : File.basename(path)
-
1306
raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
-
1306
$1.to_sym
-
end
-
1306
variable_counter = :"#{variable}_counter" if @collection
-
1306
[variable, variable_counter]
-
end
-
-
1
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
-
"make sure your partial name starts with a lowercase letter or underscore, " +
-
"and is followed by any combination of letters, numbers and underscores."
-
-
1
def raise_invalid_identifier(path)
-
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
-
end
-
end
-
end
-
1
module ActionView
-
# This is the main entry point for rendering. It basically delegates
-
# to other objects like TemplateRenderer and PartialRenderer which
-
# actually renders the template.
-
#
-
# The Renderer will parse the options from the +render+ or +render_body+
-
# method and render a partial or a template based on the options. The
-
# +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
-
# the setup and logic necessary to render a view and a new object is created
-
# each time +render+ is called.
-
1
class Renderer
-
1
attr_accessor :lookup_context
-
-
1
def initialize(lookup_context)
-
259
@lookup_context = lookup_context
-
end
-
-
# Main render entry point shared by AV and AC.
-
1
def render(context, options)
-
285
if options.key?(:partial)
-
26
render_partial(context, options)
-
else
-
259
render_template(context, options)
-
end
-
end
-
-
# Render but returns a valid Rack body. If fibers are defined, we return
-
# a streaming body that renders the template piece by piece.
-
#
-
# Note that partials are not supported to be rendered with streaming,
-
# so in such cases, we just wrap them in an array.
-
1
def render_body(context, options)
-
if options.key?(:partial)
-
[render_partial(context, options)]
-
else
-
StreamingTemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
end
-
-
# Direct accessor to template rendering.
-
1
def render_template(context, options) #:nodoc:
-
259
TemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
-
# Direct access to partial rendering.
-
1
def render_partial(context, options, &block) #:nodoc:
-
1306
PartialRenderer.new(@lookup_context).render(context, options, block)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/try'
-
-
1
module ActionView
-
1
class TemplateRenderer < AbstractRenderer #:nodoc:
-
1
def render(context, options)
-
259
@view = context
-
259
@details = extract_details(options)
-
259
template = determine_template(options)
-
259
context = @lookup_context
-
-
259
prepend_formats(template.formats)
-
-
259
unless context.rendered_format
-
222
context.rendered_format = template.formats.first || formats.first
-
end
-
-
259
render_template(template, options[:layout], options[:locals])
-
end
-
-
# Determine the template to be rendered using the given options.
-
1
def determine_template(options) #:nodoc:
-
259
keys = options.fetch(:locals, {}).keys
-
-
259
if options.key?(:text)
-
Template::Text.new(options[:text], formats.first)
-
259
elsif options.key?(:file)
-
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
-
259
elsif options.key?(:inline)
-
handler = Template.handler_for_extension(options[:type] || "erb")
-
Template.new(options[:inline], "inline template", handler, :locals => keys)
-
259
elsif options.key?(:template)
-
259
if options[:template].respond_to?(:render)
-
options[:template]
-
else
-
259
find_template(options[:template], options[:prefixes], false, keys, @details)
-
end
-
else
-
raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
-
end
-
end
-
-
# Renders the given template. A string representing the layout can be
-
# supplied as well.
-
1
def render_template(template, layout_name = nil, locals = nil) #:nodoc:
-
259
view, locals = @view, locals || {}
-
-
259
render_with_layout(layout_name, locals) do |layout|
-
259
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
-
259
template.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
1
def render_with_layout(path, locals) #:nodoc:
-
259
layout = path && find_layout(path, locals.keys)
-
259
content = yield(layout)
-
-
259
if layout
-
255
view = @view
-
255
view.view_flow.set(:layout, content)
-
765
layout.render(view, locals){ |*name| view._layout_for(*name) }
-
else
-
4
content
-
end
-
end
-
-
# This is the method which actually finds the layout using details in the lookup
-
# context object. If no layout is found, it checks if at least a layout with
-
# the given name exists across all details before raising the error.
-
1
def find_layout(layout, keys)
-
518
with_layout_format { resolve_layout(layout, keys) }
-
end
-
-
1
def resolve_layout(layout, keys)
-
518
case layout
-
when String
-
begin
-
if layout =~ /^\//
-
with_fallbacks { find_template(layout, nil, false, keys, @details) }
-
else
-
find_template(layout, nil, false, keys, @details)
-
end
-
rescue ActionView::MissingTemplate
-
all_details = @details.merge(:formats => @lookup_context.default_formats)
-
raise unless template_exists?(layout, nil, false, keys, all_details)
-
end
-
when Proc
-
259
resolve_layout(layout.call, keys)
-
when FalseClass
-
nil
-
else
-
259
layout
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActiveModel
-
# == Active \Model \Callbacks
-
#
-
# Provides an interface for any class to have Active Record like callbacks.
-
#
-
# Like the Active Record methods, the callback chain is aborted as soon as
-
# one of the methods in the chain returns +false+.
-
#
-
# First, extend ActiveModel::Callbacks from the class you are creating:
-
#
-
# class MyModel
-
# extend ActiveModel::Callbacks
-
# end
-
#
-
# Then define a list of methods that you want callbacks attached to:
-
#
-
# define_model_callbacks :create, :update
-
#
-
# This will provide all three standard callbacks (before, around and after)
-
# for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
-
# you need to wrap the methods you want callbacks on in a block so that the
-
# callbacks get a chance to fire:
-
#
-
# def create
-
# run_callbacks :create do
-
# # Your create action methods here
-
# end
-
# end
-
#
-
# Then in your class, you can use the +before_create+, +after_create+ and
-
# +around_create+ methods, just as you would in an Active Record module.
-
#
-
# before_create :action_before_create
-
#
-
# def action_before_create
-
# # Your code here
-
# end
-
#
-
# When defining an around callback remember to yield to the block, otherwise
-
# it won't be executed:
-
#
-
# around_create :log_status
-
#
-
# def log_status
-
# puts 'going to call the block...'
-
# yield
-
# puts 'block successfully called.'
-
# end
-
#
-
# You can choose not to have all three callbacks by passing a hash to the
-
# +define_model_callbacks+ method.
-
#
-
# define_model_callbacks :create, only: [:after, :before]
-
#
-
# Would only create the +after_create+ and +before_create+ callback methods in
-
# your class.
-
1
module Callbacks
-
1
def self.extended(base) #:nodoc:
-
1
base.class_eval do
-
1
include ActiveSupport::Callbacks
-
end
-
end
-
-
# define_model_callbacks accepts the same options +define_callbacks+ does,
-
# in case you want to overwrite a default. Besides that, it also accepts an
-
# <tt>:only</tt> option, where you can choose if you want all types (before,
-
# around or after) or just some.
-
#
-
# define_model_callbacks :initializer, only: :after
-
#
-
# Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
-
# on that method call. To get around this you can call the define_model_callbacks
-
# method as many times as you need.
-
#
-
# define_model_callbacks :create, only: :after
-
# define_model_callbacks :update, only: :before
-
# define_model_callbacks :destroy, only: :around
-
#
-
# Would create +after_create+, +before_update+ and +around_destroy+ methods
-
# only.
-
#
-
# You can pass in a class to before_<type>, after_<type> and around_<type>,
-
# in which case the callback will call that class's <action>_<type> method
-
# passing the object that the callback is being called on.
-
#
-
# class MyModel
-
# extend ActiveModel::Callbacks
-
# define_model_callbacks :create
-
#
-
# before_create AnotherClass
-
# end
-
#
-
# class AnotherClass
-
# def self.before_create( obj )
-
# # obj is the MyModel instance that the callback is being called on
-
# end
-
# end
-
1
def define_model_callbacks(*callbacks)
-
2
options = callbacks.extract_options!
-
2
options = {
-
:terminator => "result == false",
-
:skip_after_callbacks_if_terminated => true,
-
:scope => [:kind, :name],
-
:only => [:before, :around, :after]
-
}.merge!(options)
-
-
2
types = Array(options.delete(:only))
-
-
2
callbacks.each do |callback|
-
7
define_callbacks(callback, options)
-
-
7
types.each do |type|
-
15
send("_define_#{type}_model_callback", self, callback)
-
end
-
end
-
end
-
-
1
private
-
-
1
def _define_before_model_callback(klass, callback) #:nodoc:
-
4
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
-
def self.before_#{callback}(*args, &block)
-
set_callback(:#{callback}, :before, *args, &block)
-
end
-
CALLBACK
-
end
-
-
1
def _define_around_model_callback(klass, callback) #:nodoc:
-
4
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
-
def self.around_#{callback}(*args, &block)
-
set_callback(:#{callback}, :around, *args, &block)
-
end
-
CALLBACK
-
end
-
-
1
def _define_after_model_callback(klass, callback) #:nodoc:
-
7
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
-
def self.after_#{callback}(*args, &block)
-
options = args.extract_options!
-
options[:prepend] = true
-
options[:if] = Array(options[:if]) << "value != false"
-
set_callback(:#{callback}, :after, *(args << options), &block)
-
end
-
CALLBACK
-
end
-
end
-
end
-
1
module ActiveModel
-
# == Active \Model Conversion
-
#
-
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
-
#
-
# Let's take for example this non-persisted object.
-
#
-
# class ContactMessage
-
# include ActiveModel::Conversion
-
#
-
# # ContactMessage are never persisted in the DB
-
# def persisted?
-
# false
-
# end
-
# end
-
#
-
# cm = ContactMessage.new
-
# cm.to_model == cm # => true
-
# cm.to_key # => nil
-
# cm.to_param # => nil
-
# cm.to_partial_path # => "contact_messages/contact_message"
-
1
module Conversion
-
1
extend ActiveSupport::Concern
-
-
# If your object is already designed to implement all of the Active Model
-
# you can use the default <tt>:to_model</tt> implementation, which simply
-
# returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# end
-
#
-
# person = Person.new
-
# person.to_model == person # => true
-
#
-
# If your model does not act like an Active Model object, then you should
-
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
-
# your object with Active Model compliant methods.
-
1
def to_model
-
2226
self
-
end
-
-
# Returns an Enumerable of all key attributes if any is set, regardless if
-
# the object is persisted or not. If there no key attributes, returns +nil+.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.create
-
# person.to_key # => [1]
-
1
def to_key
-
key = respond_to?(:id) && id
-
key ? [key] : nil
-
end
-
-
# Returns a +string+ representing the object's key suitable for use in URLs,
-
# or +nil+ if <tt>persisted?</tt> is +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.create
-
# person.to_param # => "1"
-
1
def to_param
-
(persisted? && key = to_key) ? key.join('-') : nil
-
end
-
-
# Returns a +string+ identifying the path associated with the object.
-
# ActionPack uses this to find a suitable partial to represent the object.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# end
-
#
-
# person = Person.new
-
# person.to_partial_path # => "people/person"
-
1
def to_partial_path
-
103
self.class._to_partial_path
-
end
-
-
1
module ClassMethods #:nodoc:
-
# Provide a class level cache for #to_partial_path. This is an
-
# internal method and should not be accessed directly.
-
1
def _to_partial_path #:nodoc:
-
@_to_partial_path ||= begin
-
2
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self))
-
2
collection = ActiveSupport::Inflector.tableize(self)
-
2
"#{collection}/#{element}".freeze
-
103
end
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module DeprecatedMassAssignmentSecurity # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods # :nodoc:
-
1
def attr_protected(*args)
-
raise "`attr_protected` is extracted out of Rails into a gem. " \
-
"Please use new recommended protection model for params" \
-
"(strong_parameters) or add `protected_attributes` to your " \
-
"Gemfile to use old one."
-
end
-
-
1
def attr_accessible(*args)
-
raise "`attr_accessible` is extracted out of Rails into a gem. " \
-
"Please use new recommended protection model for params" \
-
"(strong_parameters) or add `protected_attributes` to your " \
-
"Gemfile to use old one."
-
end
-
end
-
end
-
end
-
1
require 'active_support/hash_with_indifferent_access'
-
1
require 'active_support/core_ext/object/duplicable'
-
-
1
module ActiveModel
-
# == Active \Model \Dirty
-
#
-
# Provides a way to track changes in your object in the same way as
-
# Active Record does.
-
#
-
# The requirements for implementing ActiveModel::Dirty are:
-
#
-
# * <tt>include ActiveModel::Dirty</tt> in your object.
-
# * Call <tt>define_attribute_methods</tt> passing each method you want to
-
# track.
-
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
-
# attribute.
-
#
-
# If you wish to also track previous changes on save or update, you need to
-
# add:
-
#
-
# @previously_changed = changes
-
#
-
# inside of your save or update method.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Dirty
-
#
-
# define_attribute_methods :name
-
#
-
# def name
-
# @name
-
# end
-
#
-
# def name=(val)
-
# name_will_change! unless val == @name
-
# @name = val
-
# end
-
#
-
# def save
-
# @previously_changed = changes
-
# @changed_attributes.clear
-
# end
-
# end
-
#
-
# A newly instantiated object is unchanged:
-
#
-
# person = Person.find_by(name: 'Uncle Bob')
-
# person.changed? # => false
-
#
-
# Change the name:
-
#
-
# person.name = 'Bob'
-
# person.changed? # => true
-
# person.name_changed? # => true
-
# person.name_was # => "Uncle Bob"
-
# person.name_change # => ["Uncle Bob", "Bob"]
-
# person.name = 'Bill'
-
# person.name_change # => ["Uncle Bob", "Bill"]
-
#
-
# Save the changes:
-
#
-
# person.save
-
# person.changed? # => false
-
# person.name_changed? # => false
-
#
-
# Assigning the same value leaves the attribute unchanged:
-
#
-
# person.name = 'Bill'
-
# person.name_changed? # => false
-
# person.name_change # => nil
-
#
-
# Which attributes have changed?
-
#
-
# person.name = 'Bob'
-
# person.changed # => ["name"]
-
# person.changes # => {"name" => ["Bill", "Bob"]}
-
#
-
# If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
-
# to mark that the attribute is changing. Otherwise ActiveModel can't track
-
# changes to in-place attributes.
-
#
-
# person.name_will_change!
-
# person.name_change # => ["Bill", "Bill"]
-
# person.name << 'y'
-
# person.name_change # => ["Bill", "Billy"]
-
1
module Dirty
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::AttributeMethods
-
-
1
included do
-
1
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
-
1
attribute_method_affix :prefix => 'reset_', :suffix => '!'
-
end
-
-
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
-
#
-
# person.changed? # => false
-
# person.name = 'bob'
-
# person.changed? # => true
-
1
def changed?
-
96
changed_attributes.present?
-
end
-
-
# Returns an array with the name of the attributes with unsaved changes.
-
#
-
# person.changed # => []
-
# person.name = 'bob'
-
# person.changed # => ["name"]
-
1
def changed
-
918
changed_attributes.keys
-
end
-
-
# Returns a hash of changed attributes indicating their original
-
# and new values like <tt>attr => [original value, new value]</tt>.
-
#
-
# person.changes # => {}
-
# person.name = 'bob'
-
# person.changes # => { "name" => ["bill", "bob"] }
-
1
def changes
-
2620
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
-
end
-
-
# Returns a hash of attributes that were changed before the model was saved.
-
#
-
# person.name # => "bob"
-
# person.name = 'robert'
-
# person.save
-
# person.previous_changes # => {"name" => ["bob", "robert"]}
-
1
def previous_changes
-
@previously_changed
-
end
-
-
# Returns a hash of the attributes with unsaved changes indicating their original
-
# values like <tt>attr => original value</tt>.
-
#
-
# person.name # => "bob"
-
# person.name = 'robert'
-
# person.changed_attributes # => {"name" => "bob"}
-
1
def changed_attributes
-
8629
@changed_attributes ||= {}
-
end
-
-
1
private
-
-
# Handle <tt>*_changed?</tt> for +method_missing+.
-
1
def attribute_changed?(attr)
-
5383
changed_attributes.include?(attr)
-
end
-
-
# Handle <tt>*_change</tt> for +method_missing+.
-
1
def attribute_change(attr)
-
2230
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
-
end
-
-
# Handle <tt>*_was</tt> for +method_missing+.
-
1
def attribute_was(attr)
-
74
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
-
end
-
-
# Handle <tt>*_will_change!</tt> for +method_missing+.
-
1
def attribute_will_change!(attr)
-
12
return if attribute_changed?(attr)
-
-
begin
-
value = __send__(attr)
-
value = value.duplicable? ? value.clone : value
-
rescue TypeError, NoMethodError
-
end
-
-
changed_attributes[attr] = value
-
end
-
-
# Handle <tt>reset_*!</tt> for +method_missing+.
-
1
def reset_attribute!(attr)
-
if attribute_changed?(attr)
-
__send__("#{attr}=", changed_attributes[attr])
-
changed_attributes.delete(attr)
-
end
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveModel
-
# == Active \Model \Errors
-
#
-
# Provides a modified +Hash+ that you can include in your object
-
# for handling error messages and interacting with Action Pack helpers.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# # Required dependency for ActiveModel::Errors
-
# extend ActiveModel::Naming
-
#
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
#
-
# attr_accessor :name
-
# attr_reader :errors
-
#
-
# def validate!
-
# errors.add(:name, "can not be nil") if name == nil
-
# end
-
#
-
# # The following methods are needed to be minimally implemented
-
#
-
# def read_attribute_for_validation(attr)
-
# send(attr)
-
# end
-
#
-
# def Person.human_attribute_name(attr, options = {})
-
# attr
-
# end
-
#
-
# def Person.lookup_ancestors
-
# [self]
-
# end
-
# end
-
#
-
# The last three methods are required in your object for Errors to be
-
# able to generate error messages correctly and also handle multiple
-
# languages. Of course, if you extend your object with ActiveModel::Translation
-
# you will not need to implement the last two. Likewise, using
-
# ActiveModel::Validations will handle the validation related methods
-
# for you.
-
#
-
# The above allows you to do:
-
#
-
# person = Person.new
-
# person.validate! # => ["can not be nil"]
-
# person.errors.full_messages # => ["name can not be nil"]
-
# # etc..
-
1
class Errors
-
1
include Enumerable
-
-
1
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
-
-
1
attr_reader :messages
-
-
# Pass in the instance of the object that is using the errors object.
-
#
-
# class Person
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
# end
-
1
def initialize(base)
-
433
@base = base
-
433
@messages = {}
-
end
-
-
1
def initialize_dup(other) # :nodoc:
-
@messages = other.messages.dup
-
super
-
end
-
-
# Clear the error messages.
-
#
-
# person.errors.full_messages # => ["name can not be nil"]
-
# person.errors.clear
-
# person.errors.full_messages # => []
-
1
def clear
-
363
messages.clear
-
end
-
-
# Returns +true+ if the error messages include an error for the given key
-
# +attribute+, +false+ otherwise.
-
#
-
# person.errors.messages # => {:name=>["can not be nil"]}
-
# person.errors.include?(:name) # => true
-
# person.errors.include?(:age) # => false
-
1
def include?(attribute)
-
(v = messages[attribute]) && v.any?
-
end
-
# aliases include?
-
1
alias :has_key? :include?
-
-
# Get messages for +key+.
-
#
-
# person.errors.messages # => {:name=>["can not be nil"]}
-
# person.errors.get(:name) # => ["can not be nil"]
-
# person.errors.get(:age) # => nil
-
1
def get(key)
-
563
messages[key]
-
end
-
-
# Set messages for +key+ to +value+.
-
#
-
# person.errors.get(:name) # => ["can not be nil"]
-
# person.errors.set(:name, ["can't be nil"])
-
# person.errors.get(:name) # => ["can't be nil"]
-
1
def set(key, value)
-
250
messages[key] = value
-
end
-
-
# Delete messages for +key+. Returns the deleted messages.
-
#
-
# person.errors.get(:name) # => ["can not be nil"]
-
# person.errors.delete(:name) # => ["can not be nil"]
-
# person.errors.get(:name) # => nil
-
1
def delete(key)
-
messages.delete(key)
-
end
-
-
# When passed a symbol or a name of a method, returns an array of errors
-
# for the method.
-
#
-
# person.errors[:name] # => ["can not be nil"]
-
# person.errors['name'] # => ["can not be nil"]
-
1
def [](attribute)
-
563
get(attribute.to_sym) || set(attribute.to_sym, [])
-
end
-
-
# Adds to the supplied attribute the supplied error message.
-
#
-
# person.errors[:name] = "must be set"
-
# person.errors[:name] # => ['must be set']
-
1
def []=(attribute, error)
-
self[attribute] << error
-
end
-
-
# Iterates through each error key, value pair in the error messages hash.
-
# Yields the attribute and the error for that attribute. If the attribute
-
# has more than one error message, yields once for each error message.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# end
-
#
-
# person.errors.add(:name, "must be specified")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# # then yield :name and "must be specified"
-
# end
-
1
def each
-
847
messages.each_key do |attribute|
-
286
self[attribute].each { |error| yield attribute, error }
-
end
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.size # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.size # => 2
-
1
def size
-
values.flatten.size
-
end
-
-
# Returns all message values.
-
#
-
# person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
-
# person.errors.values # => [["can not be nil", "must be specified"]]
-
1
def values
-
messages.values
-
end
-
-
# Returns all message keys.
-
#
-
# person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
-
# person.errors.keys # => [:name]
-
1
def keys
-
messages.keys
-
end
-
-
# Returns an array of error messages, with the attribute name included.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
-
1
def to_a
-
7
full_messages
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.count # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.count # => 2
-
1
def count
-
7
to_a.size
-
end
-
-
# Returns +true+ if no errors are found, +false+ otherwise.
-
# If the error message is a string it can be empty.
-
#
-
# person.errors.full_messages # => ["name can not be nil"]
-
# person.errors.empty? # => false
-
1
def empty?
-
800
all? { |k, v| v && v.empty? && !v.is_a?(String) }
-
end
-
# aliases empty?
-
1
alias_method :blank?, :empty?
-
-
# Returns an xml formatted representation of the Errors hash.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_xml
-
# # =>
-
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
-
# # <errors>
-
# # <error>name can't be blank</error>
-
# # <error>name must be specified</error>
-
# # </errors>
-
1
def to_xml(options={})
-
to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
-
end
-
-
# Returns a Hash that can be used as the JSON representation for this
-
# object. You can pass the <tt>:full_messages</tt> option. This determines
-
# if the json object should contain full messages or not (false by default).
-
#
-
# person.errors.as_json # => {:name=>["can not be nil"]}
-
# person.errors.as_json(full_messages: true) # => {:name=>["name can not be nil"]}
-
1
def as_json(options=nil)
-
to_hash(options && options[:full_messages])
-
end
-
-
# Returns a Hash of attributes with their error messages. If +full_messages+
-
# is +true+, it will contain full messages (see +full_message+).
-
#
-
# person.errors.to_hash # => {:name=>["can not be nil"]}
-
# person.errors.to_hash(true) # => {:name=>["name can not be nil"]}
-
1
def to_hash(full_messages = false)
-
if full_messages
-
messages = {}
-
self.messages.each do |attribute, array|
-
messages[attribute] = array.map { |message| full_message(attribute, message) }
-
end
-
messages
-
else
-
self.messages.dup
-
end
-
end
-
-
# Adds +message+ to the error messages on +attribute+. More than one error
-
# can be added to the same +attribute+. If no +message+ is supplied,
-
# <tt>:invalid</tt> is assumed.
-
#
-
# person.errors.add(:name)
-
# # => ["is invalid"]
-
# person.errors.add(:name, 'must be implemented')
-
# # => ["is invalid", "must be implemented"]
-
#
-
# person.errors.messages
-
# # => {:name=>["must be implemented", "is invalid"]}
-
#
-
# If +message+ is a symbol, it will be translated using the appropriate
-
# scope (see +generate_message+).
-
#
-
# If +message+ is a proc, it will be called, allowing for things like
-
# <tt>Time.now</tt> to be used within an error.
-
#
-
# If the <tt>:strict</tt> option is set to true will raise
-
# ActiveModel::StrictValidationFailed instead of adding the error.
-
# <tt>:strict</tt> option can also be set to any other exception.
-
#
-
# person.errors.add(:name, nil, strict: true)
-
# # => ActiveModel::StrictValidationFailed: name is invalid
-
# person.errors.add(:name, nil, strict: NameIsInvalid)
-
# # => NameIsInvalid: name is invalid
-
#
-
# person.errors.messages # => {}
-
1
def add(attribute, message = nil, options = {})
-
70
message = normalize_message(attribute, message, options)
-
70
if exception = options[:strict]
-
exception = ActiveModel::StrictValidationFailed if exception == true
-
raise exception, full_message(attribute, message)
-
end
-
-
70
self[attribute] << message
-
end
-
-
# Will add an error message to each of the attributes in +attributes+
-
# that is empty.
-
#
-
# person.errors.add_on_empty(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be empty"]}
-
1
def add_on_empty(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
is_empty = value.respond_to?(:empty?) ? value.empty? : false
-
add(attribute, :empty, options) if value.nil? || is_empty
-
end
-
end
-
-
# Will add an error message to each of the attributes in +attributes+ that
-
# is blank (using Object#blank?).
-
#
-
# person.errors.add_on_blank(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be blank"]}
-
1
def add_on_blank(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
add(attribute, :blank, options) if value.blank?
-
end
-
end
-
-
# Returns +true+ if an error on the attribute with the given message is
-
# present, +false+ otherwise. +message+ is treated the same as for +add+.
-
#
-
# person.errors.add :name, :blank
-
# person.errors.added? :name, :blank # => true
-
1
def added?(attribute, message = nil, options = {})
-
message = normalize_message(attribute, message, options)
-
self[attribute].include? message
-
end
-
-
# Returns all the full error messages in an array.
-
#
-
# class Person
-
# validates_presence_of :name, :address, :email
-
# validates_length_of :name, in: 5..30
-
# end
-
#
-
# person = Person.create(address: '123 First St.')
-
# person.errors.full_messages
-
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
-
1
def full_messages
-
88
map { |attribute, message| full_message(attribute, message) }
-
end
-
-
# Returns all the full error messages for a given attribute in an array.
-
#
-
# class Person
-
# validates_presence_of :name, :email
-
# validates_length_of :name, in: 5..30
-
# end
-
#
-
# person = Person.create()
-
# person.errors.full_messages_for(:name)
-
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
-
1
def full_messages_for(attribute)
-
(get(attribute) || []).map { |message| full_message(attribute, message) }
-
end
-
-
# Returns a full message for a given attribute.
-
#
-
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
-
1
def full_message(attribute, message)
-
74
return message if attribute == :base
-
74
attr_name = attribute.to_s.tr('.', '_').humanize
-
74
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
-
74
I18n.t(:"errors.format", {
-
:default => "%{attribute} %{message}",
-
:attribute => attr_name,
-
:message => message
-
})
-
end
-
-
# Translates an error message in its default scope
-
# (<tt>activemodel.errors.messages</tt>).
-
#
-
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
-
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
-
# that is not there also, it returns the translation of the default message
-
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
-
# name, translated attribute name and the value are available for
-
# interpolation.
-
#
-
# When using inheritance in your models, it will check all the inherited
-
# models too, but only if the model itself hasn't been found. Say you have
-
# <tt>class Admin < User; end</tt> and you wanted the translation for
-
# the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
-
# it looks for these translations:
-
#
-
# * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.admin.blank</tt>
-
# * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.user.blank</tt>
-
# * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
-
# * <tt>activemodel.errors.messages.blank</tt>
-
# * <tt>errors.attributes.title.blank</tt>
-
# * <tt>errors.messages.blank</tt>
-
1
def generate_message(attribute, type = :invalid, options = {})
-
70
type = options.delete(:message) if options[:message].is_a?(Symbol)
-
-
70
if @base.class.respond_to?(:i18n_scope)
-
70
defaults = @base.class.lookup_ancestors.map do |klass|
-
70
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
-
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
-
end
-
else
-
defaults = []
-
end
-
-
70
defaults << options.delete(:message)
-
70
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
-
70
defaults << :"errors.attributes.#{attribute}.#{type}"
-
70
defaults << :"errors.messages.#{type}"
-
-
70
defaults.compact!
-
70
defaults.flatten!
-
-
70
key = defaults.shift
-
70
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
-
-
70
options = {
-
:default => defaults,
-
:model => @base.class.model_name.human,
-
:attribute => @base.class.human_attribute_name(attribute),
-
:value => value
-
}.merge!(options)
-
-
70
I18n.translate(key, options)
-
end
-
-
1
private
-
1
def normalize_message(attribute, message, options)
-
70
message ||= :invalid
-
-
70
case message
-
when Symbol
-
70
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
-
when Proc
-
message.call
-
else
-
message
-
end
-
end
-
end
-
-
# Raised when a validation cannot be corrected by end users and are considered
-
# exceptional.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
#
-
# validates_presence_of :name, strict: true
-
# end
-
#
-
# person = Person.new
-
# person.name = nil
-
# person.valid?
-
# # => ActiveModel::StrictValidationFailed: Name can't be blank
-
1
class StrictValidationFailed < StandardError
-
end
-
end
-
1
module ActiveModel
-
# Raised when forbidden attributes are used for mass assignment.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# params = ActionController::Parameters.new(name: 'Bob')
-
# Person.new(params)
-
# # => ActiveModel::ForbiddenAttributesError
-
#
-
# params.permit!
-
# Person.new(params)
-
# # => #<Person id: nil, name: "Bob">
-
1
class ForbiddenAttributesError < StandardError
-
end
-
-
1
module ForbiddenAttributesProtection # :nodoc:
-
1
protected
-
1
def sanitize_for_mass_assignment(attributes)
-
295
if attributes.respond_to?(:permitted?) && !attributes.permitted?
-
raise ActiveModel::ForbiddenAttributesError
-
else
-
295
attributes
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/module/introspection'
-
-
1
module ActiveModel
-
1
class Name
-
1
include Comparable
-
-
1
attr_reader :singular, :plural, :element, :collection,
-
:singular_route_key, :route_key, :param_key, :i18n_key,
-
:name
-
-
1
alias_method :cache_key, :collection
-
-
##
-
# :method: ==
-
#
-
# :call-seq:
-
# ==(other)
-
#
-
# Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
-
# +other+ are equal, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name == 'BlogPost' # => true
-
# BlogPost.model_name == 'Blog Post' # => false
-
-
##
-
# :method: ===
-
#
-
# :call-seq:
-
# ===(other)
-
#
-
# Equivalent to <tt>#==</tt>.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name === 'BlogPost' # => true
-
# BlogPost.model_name === 'Blog Post' # => false
-
-
##
-
# :method: <=>
-
#
-
# :call-seq:
-
# ==(other)
-
#
-
# Equivalent to <tt>String#<=></tt>.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name <=> 'BlogPost' # => 0
-
# BlogPost.model_name <=> 'Blog' # => 1
-
# BlogPost.model_name <=> 'BlogPosts' # => -1
-
-
##
-
# :method: =~
-
#
-
# :call-seq:
-
# =~(regexp)
-
#
-
# Equivalent to <tt>String#=~</tt>. Match the class name against the given
-
# regexp. Returns the position where the match starts or +nil+ if there is
-
# no match.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name =~ /Post/ # => 4
-
# BlogPost.model_name =~ /\d/ # => nil
-
-
##
-
# :method: !~
-
#
-
# :call-seq:
-
# !~(regexp)
-
#
-
# Equivalent to <tt>String#!~</tt>. Match the class name against the given
-
# regexp. Returns +true+ if there is no match, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name !~ /Post/ # => false
-
# BlogPost.model_name !~ /\d/ # => true
-
-
##
-
# :method: eql?
-
#
-
# :call-seq:
-
# eql?(other)
-
#
-
# Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
-
# +other+ have the same length and content, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.eql?('BlogPost') # => true
-
# BlogPost.model_name.eql?('Blog Post') # => false
-
-
##
-
# :method: to_s
-
#
-
# :call-seq:
-
# to_s()
-
#
-
# Returns the class name.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.to_s # => "BlogPost"
-
-
##
-
# :method: to_str
-
#
-
# :call-seq:
-
# to_str()
-
#
-
# Equivalent to +to_s+.
-
1
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
-
:to_str, :to => :name
-
-
# Returns a new ActiveModel::Name instance. By default, the +namespace+
-
# and +name+ option will take the namespace and name of the given class
-
# respectively.
-
#
-
# module Foo
-
# class Bar
-
# end
-
# end
-
#
-
# ActiveModel::Name.new(Foo::Bar).to_s
-
# # => "Foo::Bar"
-
1
def initialize(klass, namespace = nil, name = nil)
-
4
@name = name || klass.name
-
-
4
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
-
-
4
@unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
-
4
@klass = klass
-
4
@singular = _singularize(@name)
-
4
@plural = ActiveSupport::Inflector.pluralize(@singular)
-
4
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
-
4
@human = ActiveSupport::Inflector.humanize(@element)
-
4
@collection = ActiveSupport::Inflector.tableize(@name)
-
4
@param_key = (namespace ? _singularize(@unnamespaced) : @singular)
-
4
@i18n_key = @name.underscore.to_sym
-
-
4
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
-
4
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
-
4
@route_key << "_index" if @plural == @singular
-
end
-
-
# Transform the model name into a more humane format, using I18n. By default,
-
# it will underscore then humanize the class name.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.human # => "Blog post"
-
#
-
# Specify +options+ with additional translating options.
-
1
def human(options={})
-
return @human unless @klass.respond_to?(:lookup_ancestors) &&
-
70
@klass.respond_to?(:i18n_scope)
-
-
70
defaults = @klass.lookup_ancestors.map do |klass|
-
70
klass.model_name.i18n_key
-
end
-
-
70
defaults << options[:default] if options[:default]
-
70
defaults << @human
-
-
70
options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default))
-
70
I18n.translate(defaults.shift, options)
-
end
-
-
1
private
-
-
1
def _singularize(string, replacement='_')
-
4
ActiveSupport::Inflector.underscore(string).tr('/', replacement)
-
end
-
end
-
-
# == Active \Model \Naming
-
#
-
# Creates a +model_name+ method on your object.
-
#
-
# To implement, just extend ActiveModel::Naming in your object:
-
#
-
# class BookCover
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BookCover.model_name # => "BookCover"
-
# BookCover.model_name.human # => "Book cover"
-
#
-
# BookCover.model_name.i18n_key # => :book_cover
-
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
-
#
-
# Providing the functionality that ActiveModel::Naming provides in your object
-
# is required to pass the Active Model Lint test. So either extending the
-
# provided method below, or rolling your own is required.
-
1
module Naming
-
# Returns an ActiveModel::Name object for module. It can be
-
# used to retrieve all kinds of naming-related information
-
# (See ActiveModel::Name for more information).
-
#
-
# class Person < ActiveModel::Model
-
# end
-
#
-
# Person.model_name # => Person
-
# Person.model_name.class # => ActiveModel::Name
-
# Person.model_name.singular # => "person"
-
# Person.model_name.plural # => "people"
-
1
def model_name
-
@_model_name ||= begin
-
4
namespace = self.parents.detect do |n|
-
4
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
-
end
-
4
ActiveModel::Name.new(self, namespace)
-
1447
end
-
end
-
-
# Returns the plural class name of a record or class.
-
#
-
# ActiveModel::Naming.plural(post) # => "posts"
-
# ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
-
1
def self.plural(record_or_class)
-
model_name_from_record_or_class(record_or_class).plural
-
end
-
-
# Returns the singular class name of a record or class.
-
#
-
# ActiveModel::Naming.singular(post) # => "post"
-
# ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
-
1
def self.singular(record_or_class)
-
model_name_from_record_or_class(record_or_class).singular
-
end
-
-
# Identifies whether the class name of a record or class is uncountable.
-
#
-
# ActiveModel::Naming.uncountable?(Sheep) # => true
-
# ActiveModel::Naming.uncountable?(Post) # => false
-
1
def self.uncountable?(record_or_class)
-
plural(record_or_class) == singular(record_or_class)
-
end
-
-
# Returns string to use while generating route names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.singular_route_key(Blog::Post) #=> "post"
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.singular_route_key(Blog::Post) #=> "blog_post"
-
1
def self.singular_route_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).singular_route_key
-
end
-
-
# Returns string to use while generating route names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.route_key(Blog::Post) #=> "posts"
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.route_key(Blog::Post) #=> "blog_posts"
-
#
-
# The route key also considers if the noun is uncountable and, in
-
# such cases, automatically appends _index.
-
1
def self.route_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).route_key
-
end
-
-
# Returns string to use for params names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.param_key(Blog::Post) #=> "post"
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.param_key(Blog::Post) #=> "blog_post"
-
1
def self.param_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).param_key
-
end
-
-
1
def self.model_name_from_record_or_class(record_or_class) #:nodoc:
-
if record_or_class.respond_to?(:model_name)
-
record_or_class.model_name
-
elsif record_or_class.respond_to?(:to_model)
-
record_or_class.to_model.class.model_name
-
else
-
record_or_class.class.model_name
-
end
-
end
-
1
private_class_method :model_name_from_record_or_class
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActiveModel
-
# == Active \Model \Serialization
-
#
-
# Provides a basic serialization to a serializable_hash for your object.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Serialization
-
#
-
# attr_accessor :name
-
#
-
# def attributes
-
# {'name' => nil}
-
# end
-
# end
-
#
-
# Which would provide you with:
-
#
-
# person = Person.new
-
# person.serializable_hash # => {"name"=>nil}
-
# person.name = "Bob"
-
# person.serializable_hash # => {"name"=>"Bob"}
-
#
-
# You need to declare an attributes hash which contains the attributes you
-
# want to serialize. Attributes must be strings, not symbols. When called,
-
# serializable hash will use instance methods that match the name of the
-
# attributes hash's keys. In order to override this behavior, take a look at
-
# the private method +read_attribute_for_serialization+.
-
#
-
# Most of the time though, you will want to include the JSON or XML
-
# serializations. Both of these modules automatically include the
-
# <tt>ActiveModel::Serialization</tt> module, so there is no need to
-
# explicitly include it.
-
#
-
# A minimal implementation including XML and JSON would be:
-
#
-
# class Person
-
# include ActiveModel::Serializers::JSON
-
# include ActiveModel::Serializers::Xml
-
#
-
# attr_accessor :name
-
#
-
# def attributes
-
# {'name' => nil}
-
# end
-
# end
-
#
-
# Which would provide you with:
-
#
-
# person = Person.new
-
# person.serializable_hash # => {"name"=>nil}
-
# person.as_json # => {"name"=>nil}
-
# person.to_json # => "{\"name\":null}"
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
#
-
# person.name = "Bob"
-
# person.serializable_hash # => {"name"=>"Bob"}
-
# person.as_json # => {"name"=>"Bob"}
-
# person.to_json # => "{\"name\":\"Bob\"}"
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
#
-
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
-
# <tt>:include</tt>. The following are all valid examples:
-
#
-
# person.serializable_hash(only: 'name')
-
# person.serializable_hash(include: :address)
-
# person.serializable_hash(include: { address: { only: 'city' }})
-
1
module Serialization
-
# Returns a serialized hash of your object.
-
#
-
# class Person
-
# include ActiveModel::Serialization
-
#
-
# attr_accessor :name, :age
-
#
-
# def attributes
-
# {'name' => nil, 'age' => nil}
-
# end
-
#
-
# def capitalized_name
-
# name.capitalize
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'bob'
-
# person.age = 22
-
# person.serializable_hash # => {"name"=>"bob", "age"=>22}
-
# person.serializable_hash(only: :name) # => {"name"=>"bob"}
-
# person.serializable_hash(except: :name) # => {"age"=>22}
-
# person.serializable_hash(methods: :capitalized_name)
-
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
-
1
def serializable_hash(options = nil)
-
options ||= {}
-
-
attribute_names = attributes.keys
-
if only = options[:only]
-
attribute_names &= Array(only).map(&:to_s)
-
elsif except = options[:except]
-
attribute_names -= Array(except).map(&:to_s)
-
end
-
-
hash = {}
-
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
-
-
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
-
-
serializable_add_includes(options) do |association, records, opts|
-
hash[association.to_s] = if records.respond_to?(:to_ary)
-
records.to_ary.map { |a| a.serializable_hash(opts) }
-
else
-
records.serializable_hash(opts)
-
end
-
end
-
-
hash
-
end
-
-
1
private
-
-
# Hook method defining how an attribute value should be retrieved for
-
# serialization. By default this is assumed to be an instance named after
-
# the attribute. Override this method in subclasses should you need to
-
# retrieve the value for a given attribute differently:
-
#
-
# class MyClass
-
# include ActiveModel::Validations
-
#
-
# def initialize(data = {})
-
# @data = data
-
# end
-
#
-
# def read_attribute_for_serialization(key)
-
# @data[key]
-
# end
-
# end
-
1
alias :read_attribute_for_serialization :send
-
-
# Add associations specified via the <tt>:include</tt> option.
-
#
-
# Expects a block that takes as arguments:
-
# +association+ - name of the association
-
# +records+ - the association record(s) to be serialized
-
# +opts+ - options for the association records
-
1
def serializable_add_includes(options = {}) #:nodoc:
-
return unless includes = options[:include]
-
-
unless includes.is_a?(Hash)
-
includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
-
end
-
-
includes.each do |association, opts|
-
if records = send(association)
-
yield association, records, opts
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/json'
-
-
1
module ActiveModel
-
1
module Serializers
-
# == Active Model JSON Serializer
-
1
module JSON
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serialization
-
-
1
included do
-
1
extend ActiveModel::Naming
-
-
1
class_attribute :include_root_in_json
-
1
self.include_root_in_json = false
-
end
-
-
# Returns a hash representing the model. Some configuration can be
-
# passed through +options+.
-
#
-
# The option <tt>include_root_in_json</tt> controls the top-level behavior
-
# of +as_json+. If +true+, +as_json+ will emit a single root node named
-
# after the object's type. The default value for <tt>include_root_in_json</tt>
-
# option is +false+.
-
#
-
# user = User.find(1)
-
# user.as_json
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true}
-
#
-
# ActiveRecord::Base.include_root_in_json = true
-
#
-
# user.as_json
-
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true } }
-
#
-
# This behavior can also be achieved by setting the <tt>:root</tt> option
-
# to +true+ as in:
-
#
-
# user = User.find(1)
-
# user.as_json(root: true)
-
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true } }
-
#
-
# Without any +options+, the returned Hash will include all the model's
-
# attributes.
-
#
-
# user = User.find(1)
-
# user.as_json
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true}
-
#
-
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
-
# the attributes included, and work similar to the +attributes+ method.
-
#
-
# user.as_json(only: [:id, :name])
-
# # => { "id" => 1, "name" => "Konata Izumi" }
-
#
-
# user.as_json(except: [:id, :created_at, :age])
-
# # => { "name" => "Konata Izumi", "awesome" => true }
-
#
-
# To include the result of some method calls on the model use <tt>:methods</tt>:
-
#
-
# user.as_json(methods: :permalink)
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "permalink" => "1-konata-izumi" }
-
#
-
# To include associations use <tt>:include</tt>:
-
#
-
# user.as_json(include: :posts)
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
-
# # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
-
#
-
# Second level and higher order associations work as well:
-
#
-
# user.as_json(include: { posts: {
-
# include: { comments: {
-
# only: :body } },
-
# only: :title } })
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
-
# # "title" => "Welcome to the weblog" },
-
# # { "comments" => [ { "body" => "Don't think too hard" } ],
-
# # "title" => "So I was thinking" } ] }
-
1
def as_json(options = nil)
-
root = if options && options.key?(:root)
-
options[:root]
-
else
-
include_root_in_json
-
end
-
-
if root
-
root = self.class.model_name.element if root == true
-
{ root => serializable_hash(options) }
-
else
-
serializable_hash(options)
-
end
-
end
-
-
# Sets the model +attributes+ from a JSON string. Returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Serializers::JSON
-
#
-
# attr_accessor :name, :age, :awesome
-
#
-
# def attributes=(hash)
-
# hash.each do |key, value|
-
# instance_variable_set("@#{key}", value)
-
# end
-
# end
-
#
-
# def attributes
-
# instance_values
-
# end
-
# end
-
#
-
# json = { name: 'bob', age: 22, awesome:true }.to_json
-
# person = Person.new
-
# person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
#
-
# The default value for +include_root+ is +false+. You can change it to
-
# +true+ if the given JSON string includes a single root node.
-
#
-
# json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
-
# person = Person.new
-
# person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
1
def from_json(json, include_root=include_root_in_json)
-
hash = ActiveSupport::JSON.decode(json)
-
hash = hash.values.first if include_root
-
self.attributes = hash
-
self
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/hash/conversions'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/time/acts_like'
-
-
1
module ActiveModel
-
1
module Serializers
-
# == Active Model XML Serializer
-
1
module Xml
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serialization
-
-
1
included do
-
1
extend ActiveModel::Naming
-
end
-
-
1
class Serializer #:nodoc:
-
1
class Attribute #:nodoc:
-
1
attr_reader :name, :value, :type
-
-
1
def initialize(name, serializable, value)
-
@name, @serializable = name, serializable
-
-
if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
-
value = value.in_time_zone
-
end
-
-
@value = value
-
@type = compute_type
-
end
-
-
1
def decorations
-
decorations = {}
-
decorations[:encoding] = 'base64' if type == :binary
-
decorations[:type] = (type == :string) ? nil : type
-
decorations[:nil] = true if value.nil?
-
decorations
-
end
-
-
1
protected
-
-
1
def compute_type
-
return if value.nil?
-
type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
-
type ||= :string if value.respond_to?(:to_str)
-
type ||= :yaml
-
type
-
end
-
end
-
-
1
class MethodAttribute < Attribute #:nodoc:
-
end
-
-
1
attr_reader :options
-
-
1
def initialize(serializable, options = nil)
-
@serializable = serializable
-
@options = options ? options.dup : {}
-
end
-
-
1
def serializable_hash
-
@serializable.serializable_hash(@options.except(:include))
-
end
-
-
1
def serializable_collection
-
methods = Array(options[:methods]).map(&:to_s)
-
serializable_hash.map do |name, value|
-
name = name.to_s
-
if methods.include?(name)
-
self.class::MethodAttribute.new(name, @serializable, value)
-
else
-
self.class::Attribute.new(name, @serializable, value)
-
end
-
end
-
end
-
-
1
def serialize
-
require 'builder' unless defined? ::Builder
-
-
options[:indent] ||= 2
-
options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
-
-
@builder = options[:builder]
-
@builder.instruct! unless options[:skip_instruct]
-
-
root = (options[:root] || @serializable.class.model_name.element).to_s
-
root = ActiveSupport::XmlMini.rename_key(root, options)
-
-
args = [root]
-
args << {:xmlns => options[:namespace]} if options[:namespace]
-
args << {:type => options[:type]} if options[:type] && !options[:skip_types]
-
-
@builder.tag!(*args) do
-
add_attributes_and_methods
-
add_includes
-
add_extra_behavior
-
add_procs
-
yield @builder if block_given?
-
end
-
end
-
-
1
private
-
-
1
def add_extra_behavior
-
end
-
-
1
def add_attributes_and_methods
-
serializable_collection.each do |attribute|
-
key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
-
ActiveSupport::XmlMini.to_tag(key, attribute.value,
-
options.merge(attribute.decorations))
-
end
-
end
-
-
1
def add_includes
-
@serializable.send(:serializable_add_includes, options) do |association, records, opts|
-
add_associations(association, records, opts)
-
end
-
end
-
-
# TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
-
1
def add_associations(association, records, opts)
-
merged_options = opts.merge(options.slice(:builder, :indent))
-
merged_options[:skip_instruct] = true
-
-
[:skip_types, :dasherize, :camelize].each do |key|
-
merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
-
end
-
-
if records.respond_to?(:to_ary)
-
records = records.to_ary
-
-
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
-
type = options[:skip_types] ? { } : {:type => "array"}
-
association_name = association.to_s.singularize
-
merged_options[:root] = association_name
-
-
if records.empty?
-
@builder.tag!(tag, type)
-
else
-
@builder.tag!(tag, type) do
-
records.each do |record|
-
if options[:skip_types]
-
record_type = {}
-
else
-
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
-
record_type = {:type => record_class}
-
end
-
-
record.to_xml merged_options.merge(record_type)
-
end
-
end
-
end
-
else
-
merged_options[:root] = association.to_s
-
-
unless records.class.to_s.underscore == association.to_s
-
merged_options[:type] = records.class.name
-
end
-
-
records.to_xml merged_options
-
end
-
end
-
-
1
def add_procs
-
if procs = options.delete(:procs)
-
Array(procs).each do |proc|
-
if proc.arity == 1
-
proc.call(options)
-
else
-
proc.call(options, @serializable)
-
end
-
end
-
end
-
end
-
end
-
-
# Returns XML representing the model. Configuration can be
-
# passed through +options+.
-
#
-
# Without any +options+, the returned XML string will include all the
-
# model's attributes.
-
#
-
# user = User.find(1)
-
# user.to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <user>
-
# <id type="integer">1</id>
-
# <name>David</name>
-
# <age type="integer">16</age>
-
# <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
-
# </user>
-
#
-
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
-
# attributes included, and work similar to the +attributes+ method.
-
#
-
# To include the result of some method calls on the model use <tt>:methods</tt>.
-
#
-
# To include associations use <tt>:include</tt>.
-
#
-
# For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
-
1
def to_xml(options = {}, &block)
-
Serializer.new(self, options).serialize(&block)
-
end
-
-
# Sets the model +attributes+ from a JSON string. Returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Serializers::Xml
-
#
-
# attr_accessor :name, :age, :awesome
-
#
-
# def attributes=(hash)
-
# hash.each do |key, value|
-
# instance_variable_set("@#{key}", value)
-
# end
-
# end
-
#
-
# def attributes
-
# instance_values
-
# end
-
# end
-
#
-
# xml = { name: 'bob', age: 22, awesome:true }.to_xml
-
# person = Person.new
-
# person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
1
def from_xml(xml)
-
self.attributes = Hash.from_xml(xml).values.first
-
self
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
# == Active \Model \Translation
-
#
-
# Provides integration between your object and the Rails internationalization
-
# (i18n) framework.
-
#
-
# A minimal implementation could be:
-
#
-
# class TranslatedPerson
-
# extend ActiveModel::Translation
-
# end
-
#
-
# TranslatedPerson.human_attribute_name('my_attribute')
-
# # => "My attribute"
-
#
-
# This also provides the required class methods for hooking into the
-
# Rails internationalization API, including being able to define a
-
# class based +i18n_scope+ and +lookup_ancestors+ to find translations in
-
# parent classes.
-
1
module Translation
-
1
include ActiveModel::Naming
-
-
# Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
-
1
def i18n_scope
-
:activemodel
-
end
-
-
# When localizing a string, it goes through the lookup returned by this
-
# method, which is used in ActiveModel::Name#human,
-
# ActiveModel::Errors#full_messages and
-
# ActiveModel::Translation#human_attribute_name.
-
1
def lookup_ancestors
-
self.ancestors.select { |x| x.respond_to?(:model_name) }
-
end
-
-
# Transforms attribute names into a more human format, such as "First name"
-
# instead of "first_name".
-
#
-
# Person.human_attribute_name("first_name") # => "First name"
-
#
-
# Specify +options+ with additional translating options.
-
1
def human_attribute_name(attribute, options = {})
-
207
options = { :count => 1 }.merge!(options)
-
207
parts = attribute.to_s.split(".")
-
207
attribute = parts.pop
-
207
namespace = parts.join("/") unless parts.empty?
-
207
attributes_scope = "#{self.i18n_scope}.attributes"
-
-
207
if namespace
-
defaults = lookup_ancestors.map do |klass|
-
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
-
end
-
defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
-
else
-
207
defaults = lookup_ancestors.map do |klass|
-
207
:"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
-
end
-
end
-
-
207
defaults << :"attributes.#{attribute}"
-
207
defaults << options.delete(:default) if options[:default]
-
207
defaults << attribute.humanize
-
-
207
options[:default] = defaults
-
207
I18n.translate(defaults.shift, options)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/hash/except'
-
-
1
module ActiveModel
-
-
# == Active \Model Validations
-
#
-
# Provides a full validation framework to your objects.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :first_name, :last_name
-
#
-
# validates_each :first_name, :last_name do |record, attr, value|
-
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
-
# end
-
# end
-
#
-
# Which provides you with the full standard validation stack that you
-
# know from Active Record:
-
#
-
# person = Person.new
-
# person.valid? # => true
-
# person.invalid? # => false
-
#
-
# person.first_name = 'zoolander'
-
# person.valid? # => false
-
# person.invalid? # => true
-
# person.errors.messages # => {first_name:["starts with z."]}
-
#
-
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
-
# method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
-
# object, so there is no need for you to do this manually.
-
1
module Validations
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
extend ActiveModel::Callbacks
-
1
extend ActiveModel::Translation
-
-
1
extend HelperMethods
-
1
include HelperMethods
-
-
1
attr_accessor :validation_context
-
1
define_callbacks :validate, :scope => :name
-
-
1
class_attribute :_validators
-
13
self._validators = Hash.new { |h,k| h[k] = [] }
-
end
-
-
1
module ClassMethods
-
# Validates each attribute against a block.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :first_name, :last_name
-
#
-
# validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
-
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
-
# end
-
# end
-
#
-
# Options:
-
# * <tt>:on</tt> - Specifies the context where this validation is active
-
# (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
-
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validates_each(*attr_names, &block)
-
validates_with BlockValidator, _merge_attributes(attr_names), &block
-
end
-
-
# Adds a validation method or block to the class. This is useful when
-
# overriding the +validate+ instance method becomes too unwieldy and
-
# you're looking for more descriptive declaration of your validations.
-
#
-
# This can be done with a symbol pointing to a method:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate :must_be_friends
-
#
-
# def must_be_friends
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# With a block which is passed with the current record to be validated:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate do |comment|
-
# comment.must_be_friends
-
# end
-
#
-
# def must_be_friends
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# Or with a block where self points to the current record to be validated:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate do
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# Options:
-
# * <tt>:on</tt> - Specifies the context where this validation is active
-
# (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
-
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validate(*args, &block)
-
30
options = args.extract_options!
-
30
if options.key?(:on)
-
1
options = options.dup
-
1
options[:if] = Array(options[:if])
-
1
options[:if].unshift("validation_context == :#{options[:on]}")
-
end
-
30
args << options
-
30
set_callback(:validate, *args, &block)
-
end
-
-
# List all validators that are being used to validate the model using
-
# +validates_with+ method.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validates_with MyValidator
-
# validates_with OtherValidator, on: :create
-
# validates_with StrictValidator, strict: true
-
# end
-
#
-
# Person.validators
-
# # => [
-
# # #<MyValidator:0x007fbff403e808 @options={}>,
-
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
-
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
-
# # ]
-
1
def validators
-
_validators.values.flatten.uniq
-
end
-
-
# Clears all of the validators and validations.
-
#
-
# Note that this will clear anything that is being used to validate
-
# the model for both the +validates_with+ and +validate+ methods.
-
# It clears the validators that are created with an invocation of
-
# +validates_with+ and the callbacks that are set by an invocation
-
# of +validate+.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validates_with MyValidator
-
# validates_with OtherValidator, on: :create
-
# validates_with StrictValidator, strict: true
-
# validate :cannot_be_robot
-
#
-
# def cannot_be_robot
-
# errors.add(:base, 'A person cannot be a robot') if person_is_robot
-
# end
-
# end
-
#
-
# Person.validators
-
# # => [
-
# # #<MyValidator:0x007fbff403e808 @options={}>,
-
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
-
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
-
# # ]
-
#
-
# If one runs Person.clear_validators! and then checks to see what
-
# validators this class has, you would obtain:
-
#
-
# Person.validators # => []
-
#
-
# Also, the callback set by +validate :cannot_be_robot+ will be erased
-
# so that:
-
#
-
# Person._validate_callbacks.empty? # => true
-
#
-
1
def clear_validators!
-
reset_callbacks(:validate)
-
_validators.clear
-
end
-
-
# List all validators that are being used to validate a specific attribute.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name , :age
-
#
-
# validates_presence_of :name
-
# validates_inclusion_of :age, in: 0..99
-
# end
-
#
-
# Person.validators_on(:name)
-
# # => [
-
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
-
# # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}>
-
# # ]
-
1
def validators_on(*attributes)
-
attributes.flat_map do |attribute|
-
_validators[attribute.to_sym]
-
end
-
end
-
-
# Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# end
-
#
-
# User.attribute_method?(:name) # => true
-
# User.attribute_method?(:age) # => false
-
1
def attribute_method?(attribute)
-
method_defined?(attribute)
-
end
-
-
# Copy validators on inheritance.
-
1
def inherited(base) #:nodoc:
-
5
dup = _validators.dup
-
5
base._validators = dup.each { |k, v| dup[k] = v.dup }
-
5
super
-
end
-
end
-
-
# Clean the +Errors+ object if instance is duped.
-
1
def initialize_dup(other) #:nodoc:
-
1
@errors = nil
-
1
super
-
end
-
-
# Returns the +Errors+ object that holds all information about attribute
-
# error messages.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.valid? # => false
-
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
-
1
def errors
-
2004
@errors ||= Errors.new(self)
-
end
-
-
# Runs all the specified validations and returns +true+ if no errors were
-
# added otherwise +false+.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid? # => false
-
# person.name = 'david'
-
# person.valid? # => true
-
#
-
# Context can optionally be supplied to define which callbacks to test
-
# against (the context is defined on the validations using <tt>:on</tt>).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name, on: :new
-
# end
-
#
-
# person = Person.new
-
# person.valid? # => true
-
# person.valid?(:new) # => false
-
1
def valid?(context = nil)
-
363
current_context, self.validation_context = validation_context, context
-
363
errors.clear
-
363
run_validations!
-
ensure
-
363
self.validation_context = current_context
-
end
-
-
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
-
# added, +false+ otherwise.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.invalid? # => true
-
# person.name = 'david'
-
# person.invalid? # => false
-
#
-
# Context can optionally be supplied to define which callbacks to test
-
# against (the context is defined on the validations using <tt>:on</tt>).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name, on: :new
-
# end
-
#
-
# person = Person.new
-
# person.invalid? # => false
-
# person.invalid?(:new) # => true
-
1
def invalid?(context = nil)
-
1
!valid?(context)
-
end
-
-
# Hook method defining how an attribute value should be retrieved. By default
-
# this is assumed to be an instance named after the attribute. Override this
-
# method in subclasses should you need to retrieve the value for a given
-
# attribute differently:
-
#
-
# class MyClass
-
# include ActiveModel::Validations
-
#
-
# def initialize(data = {})
-
# @data = data
-
# end
-
#
-
# def read_attribute_for_validation(key)
-
# @data[key]
-
# end
-
# end
-
1
alias :read_attribute_for_validation :send
-
-
1
protected
-
-
1
def run_validations! #:nodoc:
-
363
run_callbacks :validate
-
363
errors.empty?
-
end
-
end
-
end
-
-
14
Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
-
1
module ActiveModel
-
1
module Validations
-
# == Active Model Absence Validator
-
1
class AbsenceValidator < EachValidator #:nodoc:
-
1
def validate_each(record, attr_name, value)
-
record.errors.add(attr_name, :present, options) if value.present?
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the specified attributes are blank (as defined by
-
# Object#blank?). Happens by default on save.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_absence_of :first_name
-
# end
-
#
-
# The first_name attribute must be in the object and it must be blank.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_absence_of(*attr_names)
-
validates_with AbsenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class AcceptanceValidator < EachValidator # :nodoc:
-
1
def initialize(options)
-
super({ :allow_nil => true, :accept => "1" }.merge!(options))
-
end
-
-
1
def validate_each(record, attribute, value)
-
unless value == options[:accept]
-
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
-
end
-
end
-
-
1
def setup(klass)
-
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
-
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
-
klass.send(:attr_reader, *attr_readers)
-
klass.send(:attr_writer, *attr_writers)
-
end
-
end
-
-
1
module HelperMethods
-
# Encapsulates the pattern of wanting to validate the acceptance of a
-
# terms of service check box (or similar agreement).
-
#
-
# class Person < ActiveRecord::Base
-
# validates_acceptance_of :terms_of_service
-
# validates_acceptance_of :eula, message: 'must be abided'
-
# end
-
#
-
# If the database column does not exist, the +terms_of_service+ attribute
-
# is entirely virtual. This check is performed only if +terms_of_service+
-
# is not +nil+ and by default on save.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "must be
-
# accepted").
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
-
# is +true+).
-
# * <tt>:accept</tt> - Specifies value that is considered accepted.
-
# The default value is a string "1", which makes it easy to relate to
-
# an HTML checkbox. This should be set to +true+ if you are validating
-
# a database column, since the attribute is typecast from "1" to +true+
-
# before validation.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_acceptance_of(*attr_names)
-
validates_with AcceptanceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module Validations
-
# == Active \Model Validation Callbacks
-
#
-
# Provides an interface for any class to have +before_validation+ and
-
# +after_validation+ callbacks.
-
#
-
# First, include ActiveModel::Validations::Callbacks from the class you are
-
# creating:
-
#
-
# class MyModel
-
# include ActiveModel::Validations::Callbacks
-
#
-
# before_validation :do_stuff_before_validation
-
# after_validation :do_stuff_after_validation
-
# end
-
#
-
# Like other <tt>before_*</tt> callbacks if +before_validation+ returns
-
# +false+ then <tt>valid?</tt> will not be called.
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include ActiveSupport::Callbacks
-
1
define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name]
-
end
-
-
1
module ClassMethods
-
# Defines a callback that will get called right before validation
-
# happens.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# include ActiveModel::Validations::Callbacks
-
#
-
# attr_accessor :name
-
#
-
# validates_length_of :name, maximum: 6
-
#
-
# before_validation :remove_whitespaces
-
#
-
# private
-
#
-
# def remove_whitespaces
-
# name.strip!
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = ' bob '
-
# person.valid? # => true
-
# person.name # => "bob"
-
1
def before_validation(*args, &block)
-
options = args.last
-
if options.is_a?(Hash) && options[:on]
-
options[:if] = Array(options[:if])
-
options[:on] = Array(options[:on])
-
options[:if].unshift("#{options[:on]}.include? self.validation_context")
-
end
-
set_callback(:validation, :before, *args, &block)
-
end
-
-
# Defines a callback that will get called right after validation
-
# happens.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# include ActiveModel::Validations::Callbacks
-
#
-
# attr_accessor :name, :status
-
#
-
# validates_presence_of :name
-
#
-
# after_validation :set_status
-
#
-
# private
-
#
-
# def set_status
-
# self.status = errors.empty?
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid? # => false
-
# person.status # => false
-
# person.name = 'bob'
-
# person.valid? # => true
-
# person.status # => true
-
1
def after_validation(*args, &block)
-
options = args.extract_options!
-
options[:prepend] = true
-
options[:if] = Array(options[:if])
-
if options[:on]
-
options[:on] = Array(options[:on])
-
options[:if].unshift("#{options[:on]}.include? self.validation_context")
-
end
-
set_callback(:validation, :after, *(args << options), &block)
-
end
-
end
-
-
1
protected
-
-
# Overwrite run validations to include callbacks.
-
1
def run_validations! #:nodoc:
-
726
run_callbacks(:validation) { super }
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/range'
-
-
1
module ActiveModel
-
1
module Validations
-
1
module Clusivity #:nodoc:
-
1
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
-
"and must be supplied as the :in (or :within) option of the configuration hash"
-
-
1
def check_validity!
-
unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
-
raise ArgumentError, ERROR_MESSAGE
-
end
-
end
-
-
1
private
-
-
1
def include?(record, value)
-
exclusions = if delimiter.respond_to?(:call)
-
delimiter.call(record)
-
elsif delimiter.respond_to?(:to_sym)
-
record.send(delimiter)
-
else
-
delimiter
-
end
-
-
exclusions.send(inclusion_method(exclusions), value)
-
end
-
-
1
def delimiter
-
@delimiter ||= options[:in] || options[:within]
-
end
-
-
# In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
-
# possible values in the range for equality, which is slower but more accurate.
-
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
-
# endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges.
-
1
def inclusion_method(enumerable)
-
return :include? unless enumerable.is_a?(Range)
-
case enumerable.first
-
when Numeric, Time, DateTime
-
:cover?
-
else
-
:include?
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class ConfirmationValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attribute, value)
-
251
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
-
1
human_attribute_name = record.class.human_attribute_name(attribute)
-
1
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
-
end
-
end
-
-
1
def setup(klass)
-
1
klass.send(:attr_reader, *attributes.map do |attribute|
-
1
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
-
end.compact)
-
-
1
klass.send(:attr_writer, *attributes.map do |attribute|
-
1
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
-
end.compact)
-
end
-
end
-
-
1
module HelperMethods
-
# Encapsulates the pattern of wanting to validate a password or email
-
# address field with a confirmation.
-
#
-
# Model:
-
# class Person < ActiveRecord::Base
-
# validates_confirmation_of :user_name, :password
-
# validates_confirmation_of :email_address,
-
# message: 'should match confirmation'
-
# end
-
#
-
# View:
-
# <%= password_field "person", "password" %>
-
# <%= password_field "person", "password_confirmation" %>
-
#
-
# The added +password_confirmation+ attribute is virtual; it exists only
-
# as an in-memory attribute for validating the password. To achieve this,
-
# the validation adds accessors to the model for the confirmation
-
# attribute.
-
#
-
# NOTE: This check is performed only if +password_confirmation+ is not
-
# +nil+. To require confirmation, make sure to add a presence check for
-
# the confirmation attribute:
-
#
-
# validates_presence_of :password_confirmation, if: :password_changed?
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
-
# confirmation").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_confirmation_of(*attr_names)
-
1
validates_with ConfirmationValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require "active_model/validations/clusivity"
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class ExclusionValidator < EachValidator # :nodoc:
-
1
include Clusivity
-
-
1
def validate_each(record, attribute, value)
-
if include?(record, value)
-
record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(:value => value))
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the value of the specified attribute is not in a
-
# particular enumerable object.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
-
# validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
-
# validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
-
# validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
-
# message: 'should not be the same as your username or first name'
-
# validates_exclusion_of :karma, in: :reserved_karmas
-
# end
-
#
-
# Configuration options:
-
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't
-
# be part of. This can be supplied as a proc, lambda or symbol which returns an
-
# enumerable. If the enumerable is a range the test is performed with
-
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
-
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
-
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
-
# reserved").
-
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the
-
# attribute is blank(default is +false+).
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_exclusion_of(*attr_names)
-
validates_with ExclusionValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class FormatValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attribute, value)
-
518
if options[:with]
-
259
regexp = option_call(record, :with)
-
259
record_error(record, attribute, :with, value) if value.to_s !~ regexp
-
259
elsif options[:without]
-
259
regexp = option_call(record, :without)
-
259
record_error(record, attribute, :without, value) if value.to_s =~ regexp
-
end
-
end
-
-
1
def check_validity!
-
2
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
-
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
-
end
-
-
2
check_options_validity(options, :with)
-
2
check_options_validity(options, :without)
-
end
-
-
1
private
-
-
1
def option_call(record, name)
-
518
option = options[name]
-
518
option.respond_to?(:call) ? option.call(record) : option
-
end
-
-
1
def record_error(record, attribute, name, value)
-
16
record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
-
end
-
-
1
def regexp_using_multiline_anchors?(regexp)
-
regexp.source.start_with?("^") ||
-
2
(regexp.source.end_with?("$") && !regexp.source.end_with?("\\$"))
-
end
-
-
1
def check_options_validity(options, name)
-
4
option = options[name]
-
4
if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
-
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
-
4
elsif option && option.is_a?(Regexp) &&
-
regexp_using_multiline_anchors?(option) && options[:multiline] != true
-
raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
-
"which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
-
":multiline => true option?"
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is of the correct
-
# form, going by the regular expression provided.You can require that the
-
# attribute matches the regular expression:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
-
# end
-
#
-
# Alternatively, you can require that the specified attribute does _not_
-
# match the regular expression:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_format_of :email, without: /NOSPAM/
-
# end
-
#
-
# You can also provide a proc or lambda which will determine the regular
-
# expression that will be used to validate the attribute.
-
#
-
# class Person < ActiveRecord::Base
-
# # Admin can have number as a first letter in their screen name
-
# validates_format_of :screen_name,
-
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
-
# end
-
#
-
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
-
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
-
#
-
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
-
# the <tt>multiline: true</tt> option in case you use any of these two
-
# anchors in the provided regular expression. In most cases, you should be
-
# using <tt>\A</tt> and <tt>\z</tt>.
-
#
-
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
-
# In addition, both must be a regular expression or a proc or lambda, or
-
# else an exception will be raised.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
-
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the
-
# attribute is blank (default is +false+).
-
# * <tt>:with</tt> - Regular expression that if the attribute matches will
-
# result in a successful validation. This can be provided as a proc or
-
# lambda returning regular expression which will be called at runtime.
-
# * <tt>:without</tt> - Regular expression that if the attribute does not
-
# match will result in a successful validation. This can be provided as
-
# a proc or lambda returning regular expression which will be called at
-
# runtime.
-
# * <tt>:multiline</tt> - Set to true if your regular expression contains
-
# anchors that match the beginning or end of lines as opposed to the
-
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_format_of(*attr_names)
-
validates_with FormatValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require "active_model/validations/clusivity"
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class InclusionValidator < EachValidator # :nodoc:
-
1
include Clusivity
-
-
1
def validate_each(record, attribute, value)
-
unless include?(record, value)
-
record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(:value => value))
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is available in a
-
# particular enumerable object.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_inclusion_of :gender, in: %w( m f )
-
# validates_inclusion_of :age, in: 0..99
-
# validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
-
# validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
-
# validates_inclusion_of :karma, in: :available_karmas
-
# end
-
#
-
# Configuration options:
-
# * <tt>:in</tt> - An enumerable object of available items. This can be
-
# supplied as a proc, lambda or symbol which returns an enumerable. If the
-
# enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>,
-
# otherwise with <tt>include?</tt>. When using a proc or lambda the instance
-
# under validation is passed as an argument.
-
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
-
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
-
# not included in the list").
-
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
-
# attribute is blank (default is +false+).
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_inclusion_of(*attr_names)
-
validates_with InclusionValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
# == Active \Model Length \Validator
-
1
module Validations
-
1
class LengthValidator < EachValidator # :nodoc:
-
1
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
-
1
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
-
-
1
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
-
-
1
def initialize(options)
-
5
if range = (options.delete(:in) || options.delete(:within))
-
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
-
options[:minimum], options[:maximum] = range.min, range.max
-
end
-
-
5
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
-
options[:minimum] = 1
-
end
-
-
5
super
-
end
-
-
1
def check_validity!
-
5
keys = CHECKS.keys & options.keys
-
-
5
if keys.empty?
-
raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
-
end
-
-
5
keys.each do |key|
-
5
value = options[key]
-
-
5
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
-
raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
-
end
-
end
-
end
-
-
1
def validate_each(record, attribute, value)
-
851
value = tokenize(value)
-
851
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
-
851
errors_options = options.except(*RESERVED_OPTIONS)
-
-
851
CHECKS.each do |key, validity_check|
-
2553
next unless check_value = options[key]
-
-
851
if !value.nil? || skip_nil_check?(key)
-
843
next if value_length.send(validity_check, check_value)
-
end
-
-
13
errors_options[:count] = check_value
-
-
13
default_message = options[MESSAGES[key]]
-
13
errors_options[:message] ||= default_message if default_message
-
-
13
record.errors.add(attribute, MESSAGES[key], errors_options)
-
end
-
end
-
-
1
private
-
-
1
def tokenize(value)
-
if options[:tokenizer] && value.kind_of?(String)
-
options[:tokenizer].call(value)
-
851
end || value
-
end
-
-
1
def skip_nil_check?(key)
-
10
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
-
end
-
end
-
-
1
module HelperMethods
-
-
# Validates that the specified attribute matches the length restrictions
-
# supplied. Only one option can be used at a time:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_length_of :first_name, maximum: 30
-
# validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
-
# validates_length_of :fax, in: 7..32, allow_nil: true
-
# validates_length_of :phone, in: 7..32, allow_blank: true
-
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
-
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
-
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
-
# validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
-
# tokenizer: ->(str) { str.scan(/\w+/) }
-
# end
-
#
-
# Configuration options:
-
# * <tt>:minimum</tt> - The minimum size of the attribute.
-
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
-
# default if not used with :minimum.
-
# * <tt>:is</tt> - The exact size of the attribute.
-
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
-
# the attribute.
-
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
-
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
-
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
-
# * <tt>:too_long</tt> - The error message if the attribute goes over the
-
# maximum (default is: "is too long (maximum is %{count} characters)").
-
# * <tt>:too_short</tt> - The error message if the attribute goes under the
-
# minimum (default is: "is too short (min is %{count} characters)").
-
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
-
# method and the attribute is the wrong size (default is: "is the wrong
-
# length (should be %{count} characters)").
-
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
-
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
-
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
-
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
-
# (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
-
# as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
-
# which counts individual characters.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_length_of(*attr_names)
-
validates_with LengthValidator, _merge_attributes(attr_names)
-
end
-
-
1
alias_method :validates_size_of, :validates_length_of
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class NumericalityValidator < EachValidator # :nodoc:
-
1
CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
-
:equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
-
:odd => :odd?, :even => :even?, :other_than => :!= }.freeze
-
-
1
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
-
-
1
def check_validity!
-
keys = CHECKS.keys - [:odd, :even]
-
options.slice(*keys).each do |option, value|
-
next if value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
-
raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
-
end
-
end
-
-
1
def validate_each(record, attr_name, value)
-
before_type_cast = :"#{attr_name}_before_type_cast"
-
-
raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
-
raw_value ||= value
-
-
return if options[:allow_nil] && raw_value.nil?
-
-
unless value = parse_raw_value_as_a_number(raw_value)
-
record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
-
return
-
end
-
-
if options[:only_integer]
-
unless value = parse_raw_value_as_an_integer(raw_value)
-
record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
-
return
-
end
-
end
-
-
options.slice(*CHECKS.keys).each do |option, option_value|
-
case option
-
when :odd, :even
-
unless value.to_i.send(CHECKS[option])
-
record.errors.add(attr_name, option, filtered_options(value))
-
end
-
else
-
option_value = option_value.call(record) if option_value.is_a?(Proc)
-
option_value = record.send(option_value) if option_value.is_a?(Symbol)
-
-
unless value.send(CHECKS[option], option_value)
-
record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value))
-
end
-
end
-
end
-
end
-
-
1
protected
-
-
1
def parse_raw_value_as_a_number(raw_value)
-
case raw_value
-
when /\A0[xX]/
-
nil
-
else
-
begin
-
Kernel.Float(raw_value)
-
rescue ArgumentError, TypeError
-
nil
-
end
-
end
-
end
-
-
1
def parse_raw_value_as_an_integer(raw_value)
-
raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
-
end
-
-
1
def filtered_options(value)
-
options.except(*RESERVED_OPTIONS).merge!(:value => value)
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is numeric by
-
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
-
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt>
-
# (if <tt>only_integer</tt> is set to +true+).
-
#
-
# class Person < ActiveRecord::Base
-
# validates_numericality_of :value, on: :create
-
# end
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
-
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
-
# integer, e.g. an integral value (default is +false+).
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
-
# +false+). Notice that for fixnum and float columns empty strings are
-
# converted to +nil+.
-
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
-
# supplied value.
-
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
-
# greater than or equal the supplied value.
-
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
-
# value.
-
# * <tt>:less_than</tt> - Specifies the value must be less than the
-
# supplied value.
-
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
-
# than or equal the supplied value.
-
# * <tt>:other_than</tt> - Specifies the value must be other than the
-
# supplied value.
-
# * <tt>:odd</tt> - Specifies the value must be an odd number.
-
# * <tt>:even</tt> - Specifies the value must be an even number.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+ .
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
#
-
# The following checks can also be supplied with a proc or a symbol which
-
# corresponds to a method:
-
#
-
# * <tt>:greater_than</tt>
-
# * <tt>:greater_than_or_equal_to</tt>
-
# * <tt>:equal_to</tt>
-
# * <tt>:less_than</tt>
-
# * <tt>:less_than_or_equal_to</tt>
-
#
-
# For example:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_numericality_of :width, less_than: ->(person) { person.height }
-
# validates_numericality_of :width, greater_than: :minimum_weight
-
# end
-
1
def validates_numericality_of(*attr_names)
-
validates_with NumericalityValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class PresenceValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attr_name, value)
-
1478
record.errors.add(attr_name, :blank, options) if value.blank?
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the specified attributes are not blank (as defined by
-
# Object#blank?). Happens by default on save.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_presence_of :first_name
-
# end
-
#
-
# The first_name attribute must be in the object and it cannot be blank.
-
#
-
# If you want to validate the presence of a boolean field (where the real
-
# values are +true+ and +false+), you will want to use
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
-
#
-
# This is due to the way Object#blank? handles boolean values:
-
# <tt>false.blank? # => true</tt>.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_presence_of(*attr_names)
-
validates_with PresenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActiveModel
-
1
module Validations
-
1
module ClassMethods
-
# This method is a shortcut to all default validators and any custom
-
# validator classes ending in 'Validator'. Note that Rails default
-
# validators can be overridden inside specific classes by creating
-
# custom validator classes in their place such as PresenceValidator.
-
#
-
# Examples of using the default rails validators:
-
#
-
# validates :terms, acceptance: true
-
# validates :password, confirmation: true
-
# validates :username, exclusion: { in: %w(admin superuser) }
-
# validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
-
# validates :age, inclusion: { in: 0..9 }
-
# validates :first_name, length: { maximum: 30 }
-
# validates :age, numericality: true
-
# validates :username, presence: true
-
# validates :username, uniqueness: true
-
#
-
# The power of the +validates+ method comes when using custom validators
-
# and default validators in one call for a given attribute.
-
#
-
# class EmailValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, (options[:message] || "is not an email") unless
-
# value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
-
# end
-
# end
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# attr_accessor :name, :email
-
#
-
# validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
-
# validates :email, presence: true, email: true
-
# end
-
#
-
# Validator classes may also exist within the class being validated
-
# allowing custom modules of validators to be included as needed.
-
#
-
# class Film
-
# include ActiveModel::Validations
-
#
-
# class TitleValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
-
# end
-
# end
-
#
-
# validates :name, title: true
-
# end
-
#
-
# Additionally validator classes may be in another namespace and still
-
# used within any class.
-
#
-
# validates :name, :'film/title' => true
-
#
-
# The validators hash can also handle regular expressions, ranges, arrays
-
# and strings in shortcut form.
-
#
-
# validates :email, format: /@/
-
# validates :gender, inclusion: %w(male female)
-
# validates :password, length: 6..20
-
#
-
# When using shortcut form, ranges and arrays are passed to your
-
# validator's initializer as <tt>options[:in]</tt> while other types
-
# including regular expressions and strings are passed as <tt>options[:with]</tt>.
-
#
-
# There is also a list of options that could be used along with validators:
-
#
-
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
-
# validation contexts by default (+nil+), other options are <tt>:create</tt>
-
# and <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:strict</tt> - if the <tt>:strict</tt> option is set to true
-
# will raise ActiveModel::StrictValidationFailed instead of adding the error.
-
# <tt>:strict</tt> option can also be set to any other exception.
-
#
-
# Example:
-
#
-
# validates :password, presence: true, confirmation: true, if: :password_required?
-
# validates :token, uniqueness: true, strict: TokenGenerationException
-
#
-
#
-
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
-
# and +:message+ can be given to one specific validator, as a hash:
-
#
-
# validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
-
1
def validates(*attributes)
-
11
defaults = attributes.extract_options!.dup
-
11
validations = defaults.slice!(*_validates_default_keys)
-
-
11
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
-
11
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
-
-
11
defaults[:attributes] = attributes
-
-
11
validations.each do |key, options|
-
19
next unless options
-
19
key = "#{key.to_s.camelize}Validator"
-
-
19
begin
-
19
validator = key.include?('::') ? key.constantize : const_get(key)
-
rescue NameError
-
raise ArgumentError, "Unknown validator: '#{key}'"
-
end
-
-
19
validates_with(validator, defaults.merge(_parse_validates_options(options)))
-
end
-
end
-
-
# This method is used to define validations that cannot be corrected by end
-
# users and are considered exceptional. So each validator defined with bang
-
# or <tt>:strict</tt> option set to <tt>true</tt> will always raise
-
# <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
-
# when validation fails. See <tt>validates</tt> for more information about
-
# the validation itself.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates! :name, presence: true
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid?
-
# # => ActiveModel::StrictValidationFailed: Name can't be blank
-
1
def validates!(*attributes)
-
options = attributes.extract_options!
-
options[:strict] = true
-
validates(*(attributes << options))
-
end
-
-
1
protected
-
-
# When creating custom validators, it might be useful to be able to specify
-
# additional default keys. This can be done by overwriting this method.
-
1
def _validates_default_keys # :nodoc:
-
11
[:if, :unless, :on, :allow_blank, :allow_nil , :strict]
-
end
-
-
1
def _parse_validates_options(options) # :nodoc:
-
19
case options
-
when TrueClass
-
10
{}
-
when Hash
-
9
options
-
when Range, Array
-
{ :in => options }
-
else
-
{ :with => options }
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module Validations
-
1
module HelperMethods
-
1
private
-
1
def _merge_attributes(attr_names)
-
3
options = attr_names.extract_options!.symbolize_keys
-
3
attr_names.flatten!
-
3
options[:attributes] = attr_names
-
3
options
-
end
-
end
-
-
1
class WithValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attr, val)
-
method_name = options[:with]
-
-
if record.method(method_name).arity == 0
-
record.send method_name
-
else
-
record.send method_name, attr
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Passes the record off to the class or classes specified and allows them
-
# to add errors based on more complex conditions.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# if some_complex_logic
-
# record.errors.add :base, 'This record is invalid'
-
# end
-
# end
-
#
-
# private
-
# def some_complex_logic
-
# # ...
-
# end
-
# end
-
#
-
# You may also pass it multiple classes, like so:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator, MyOtherValidator, on: :create
-
# end
-
#
-
# Configuration options:
-
# * <tt>:on</tt> - Specifies when this validation is active
-
# (<tt>:create</tt> or <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
-
# The method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur
-
# (e.g. <tt>unless: :skip_validation</tt>, or
-
# <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>).
-
# The method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
-
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
-
#
-
# If you pass any additional configuration options, they will be passed
-
# to the class and available as +options+:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator, my_custom_key: 'my custom value'
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# options[:my_custom_key] # => "my custom value"
-
# end
-
# end
-
1
def validates_with(*args, &block)
-
22
options = args.extract_options!
-
22
args.each do |klass|
-
22
validator = klass.new(options, &block)
-
22
validator.setup(self) if validator.respond_to?(:setup)
-
-
22
if validator.respond_to?(:attributes) && !validator.attributes.empty?
-
22
validator.attributes.each do |attribute|
-
22
_validators[attribute.to_sym] << validator
-
end
-
else
-
_validators[nil] << validator
-
end
-
-
22
validate(validator, options)
-
end
-
end
-
end
-
-
# Passes the record off to the class or classes specified and allows them
-
# to add errors based on more complex conditions.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validate :instance_validations
-
#
-
# def instance_validations
-
# validates_with MyValidator
-
# end
-
# end
-
#
-
# Please consult the class method documentation for more information on
-
# creating your own validator.
-
#
-
# You may also pass it multiple classes, like so:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validate :instance_validations, on: :create
-
#
-
# def instance_validations
-
# validates_with MyValidator, MyOtherValidator
-
# end
-
# end
-
#
-
# Standard configuration options (<tt>:on</tt>, <tt>:if</tt> and
-
# <tt>:unless</tt>), which are available on the class version of
-
# +validates_with+, should instead be placed on the +validates+ method
-
# as these are applied and tested in the callback.
-
#
-
# If you pass any additional configuration options, they will be passed
-
# to the class and available as +options+, please refer to the
-
# class version of this method for more information.
-
1
def validates_with(*args, &block)
-
options = args.extract_options!
-
args.each do |klass|
-
validator = klass.new(options, &block)
-
validator.validate(self)
-
end
-
end
-
end
-
end
-
1
require "active_support/core_ext/module/anonymous"
-
-
1
module ActiveModel
-
-
# == Active \Model \Validator
-
#
-
# A simple base class that can be used along with
-
# ActiveModel::Validations::ClassMethods.validates_with
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# if some_complex_logic
-
# record.errors[:base] = "This record is invalid"
-
# end
-
# end
-
#
-
# private
-
# def some_complex_logic
-
# # ...
-
# end
-
# end
-
#
-
# Any class that inherits from ActiveModel::Validator must implement a method
-
# called +validate+ which accepts a +record+.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# record # => The person instance being validated
-
# options # => Any non-standard options passed to validates_with
-
# end
-
# end
-
#
-
# To cause a validation error, you must add to the +record+'s errors directly
-
# from within the validators message.
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# record.errors.add :base, "This is some custom error message"
-
# record.errors.add :first_name, "This is some complex validation"
-
# # etc...
-
# end
-
# end
-
#
-
# To add behavior to the initialize method, use the following signature:
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def initialize(options)
-
# super
-
# @my_custom_field = options[:field_name] || :first_name
-
# end
-
# end
-
#
-
# Note that the validator is initialized only once for the whole application
-
# life cycle, and not on each validation run.
-
#
-
# The easiest way to add custom validators for validating individual attributes
-
# is with the convenient <tt>ActiveModel::EachValidator</tt>.
-
#
-
# class TitleValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
-
# end
-
# end
-
#
-
# This can now be used in combination with the +validates+ method
-
# (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# attr_accessor :title
-
#
-
# validates :title, presence: true
-
# end
-
#
-
# Validator may also define a +setup+ instance method which will get called
-
# with the class that using that validator as its argument. This can be
-
# useful when there are prerequisites such as an +attr_accessor+ being present.
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def setup(klass)
-
# klass.send :attr_accessor, :custom_attribute
-
# end
-
# end
-
#
-
# This setup method is only called when used with validation macros or the
-
# class level <tt>validates_with</tt> method.
-
1
class Validator
-
1
attr_reader :options
-
-
# Returns the kind of the validator.
-
#
-
# PresenceValidator.kind # => :presence
-
# UniquenessValidator.kind # => :uniqueness
-
1
def self.kind
-
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
-
end
-
-
# Accepts options that will be made available through the +options+ reader.
-
1
def initialize(options = {})
-
22
@options = options.freeze
-
end
-
-
# Return the kind for this validator.
-
#
-
# PresenceValidator.new.kind # => :presence
-
# UniquenessValidator.new.kind # => :uniqueness
-
1
def kind
-
self.class.kind
-
end
-
-
# Override this method in subclasses with validation logic, adding errors
-
# to the records +errors+ array where necessary.
-
1
def validate(record)
-
raise NotImplementedError, "Subclasses must implement a validate(record) method."
-
end
-
end
-
-
# +EachValidator+ is a validator which iterates through the attributes given
-
# in the options hash invoking the <tt>validate_each</tt> method passing in the
-
# record, attribute and value.
-
#
-
# All Active Model validations are built on top of this validator.
-
1
class EachValidator < Validator #:nodoc:
-
1
attr_reader :attributes
-
-
# Returns a new validator instance. All options will be available via the
-
# +options+ reader, however the <tt>:attributes</tt> option will be removed
-
# and instead be made available through the +attributes+ reader.
-
1
def initialize(options)
-
22
@attributes = Array(options.delete(:attributes))
-
22
raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
-
22
super
-
22
check_validity!
-
end
-
-
# Performs validation on the supplied record. By default this will call
-
# +validates_each+ to determine validity therefore subclasses should
-
# override +validates_each+ with validation logic.
-
1
def validate(record)
-
3616
attributes.each do |attribute|
-
3616
value = record.read_attribute_for_validation(attribute)
-
3616
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
-
3616
validate_each(record, attribute, value)
-
end
-
end
-
-
# Override this method in subclasses with the validation logic, adding
-
# errors to the records +errors+ array where necessary.
-
1
def validate_each(record, attribute, value)
-
raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
-
end
-
-
# Hook method that gets called by the initializer allowing verification
-
# that the arguments supplied are valid. You could for example raise an
-
# +ArgumentError+ when invalid options are supplied.
-
1
def check_validity!
-
end
-
end
-
-
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
-
# and call this block for each attribute being validated. +validates_each+ uses this validator.
-
1
class BlockValidator < EachValidator #:nodoc:
-
1
def initialize(options, &block)
-
@block = block
-
super
-
end
-
-
1
private
-
-
1
def validate_each(record, attribute, value)
-
@block.call(record, attribute, value)
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Aggregations
-
1
module Aggregations # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
def clear_aggregation_cache #:nodoc:
-
4
@aggregation_cache.clear if persisted?
-
end
-
-
# Active Record implements aggregation through a macro-like class method called +composed_of+
-
# for representing attributes as value objects. It expresses relationships like "Account [is]
-
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
-
# to the macro adds a description of how the value objects are created from the attributes of
-
# the entity object (when the entity is initialized either as a new object or from finding an
-
# existing object) and how it can be turned back into attributes (when the entity is saved to
-
# the database).
-
#
-
# class Customer < ActiveRecord::Base
-
# composed_of :balance, class_name: "Money", mapping: %w(balance amount)
-
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
-
# end
-
#
-
# The customer class now has the following methods to manipulate the value objects:
-
# * <tt>Customer#balance, Customer#balance=(money)</tt>
-
# * <tt>Customer#address, Customer#address=(address)</tt>
-
#
-
# These methods will operate with value objects like the ones described below:
-
#
-
# class Money
-
# include Comparable
-
# attr_reader :amount, :currency
-
# EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
-
#
-
# def initialize(amount, currency = "USD")
-
# @amount, @currency = amount, currency
-
# end
-
#
-
# def exchange_to(other_currency)
-
# exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
-
# Money.new(exchanged_amount, other_currency)
-
# end
-
#
-
# def ==(other_money)
-
# amount == other_money.amount && currency == other_money.currency
-
# end
-
#
-
# def <=>(other_money)
-
# if currency == other_money.currency
-
# amount <=> other_money.amount
-
# else
-
# amount <=> other_money.exchange_to(currency).amount
-
# end
-
# end
-
# end
-
#
-
# class Address
-
# attr_reader :street, :city
-
# def initialize(street, city)
-
# @street, @city = street, city
-
# end
-
#
-
# def close_to?(other_address)
-
# city == other_address.city
-
# end
-
#
-
# def ==(other_address)
-
# city == other_address.city && street == other_address.street
-
# end
-
# end
-
#
-
# Now it's possible to access attributes from the database through the value objects instead. If
-
# you choose to name the composition the same as the attribute's name, it will be the only way to
-
# access that attribute. That's the case with our +balance+ attribute. You interact with the value
-
# objects just like you would with any other attribute:
-
#
-
# customer.balance = Money.new(20) # sets the Money value object and the attribute
-
# customer.balance # => Money value object
-
# customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
-
# customer.balance > Money.new(10) # => true
-
# customer.balance == Money.new(20) # => true
-
# customer.balance < Money.new(5) # => false
-
#
-
# Value objects can also be composed of multiple attributes, such as the case of Address. The order
-
# of the mappings will determine the order of the parameters.
-
#
-
# customer.address_street = "Hyancintvej"
-
# customer.address_city = "Copenhagen"
-
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
-
#
-
# customer.address_street = "Vesterbrogade"
-
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
-
# customer.clear_aggregation_cache
-
# customer.address # => Address.new("Vesterbrogade", "Copenhagen")
-
#
-
# customer.address = Address.new("May Street", "Chicago")
-
# customer.address_street # => "May Street"
-
# customer.address_city # => "Chicago"
-
#
-
# == Writing value objects
-
#
-
# Value objects are immutable and interchangeable objects that represent a given value, such as
-
# a Money object representing $5. Two Money objects both representing $5 should be equal (through
-
# methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
-
# unlike entity objects where equality is determined by identity. An entity class such as Customer can
-
# easily have two different objects that both have an address on Hyancintvej. Entity identity is
-
# determined by object or relational unique identifiers (such as primary keys). Normal
-
# ActiveRecord::Base classes are entity objects.
-
#
-
# It's also important to treat the value objects as immutable. Don't allow the Money object to have
-
# its amount changed after creation. Create a new Money object with the new value instead. The
-
# Money#exchange_to method is an example of this. It returns a new value object instead of changing
-
# its own values. Active Record won't persist value objects that have been changed through means
-
# other than the writer method.
-
#
-
# The immutable requirement is enforced by Active Record by freezing any object assigned as a value
-
# object. Attempting to change it afterwards will result in a RuntimeError.
-
#
-
# Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
-
# keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
-
#
-
# == Custom constructors and converters
-
#
-
# By default value objects are initialized by calling the <tt>new</tt> constructor of the value
-
# class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
-
# option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
-
# a custom constructor to be specified.
-
#
-
# When a new value is assigned to the value object, the default assumption is that the new value
-
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
-
# converted to an instance of value class if necessary.
-
#
-
# For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
-
# should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
-
# for the value class is called +create+ and it expects a CIDR address string as a parameter. New
-
# values can be assigned to the value object using either another NetAddr::CIDR object, a string
-
# or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
-
# these requirements:
-
#
-
# class NetworkResource < ActiveRecord::Base
-
# composed_of :cidr,
-
# class_name: 'NetAddr::CIDR',
-
# mapping: [ %w(network_address network), %w(cidr_range bits) ],
-
# allow_nil: true,
-
# constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
-
# converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
-
# end
-
#
-
# # This calls the :constructor
-
# network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
-
#
-
# # These assignments will both use the :converter
-
# network_resource.cidr = [ '192.168.2.1', 8 ]
-
# network_resource.cidr = '192.168.0.1/24'
-
#
-
# # This assignment won't use the :converter as the value is already an instance of the value class
-
# network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
-
#
-
# # Saving and then reloading will use the :constructor on reload
-
# network_resource.save
-
# network_resource.reload
-
#
-
# == Finding records by a value object
-
#
-
# Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
-
# by specifying an instance of the value object in the conditions hash. The following example
-
# finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
-
#
-
# Customer.where(balance: Money.new(20, "USD"))
-
#
-
1
module ClassMethods
-
# Adds reader and writer methods for manipulating a value object:
-
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
-
#
-
# Options are:
-
# * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
-
# can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
-
# to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
-
# with this option.
-
# * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
-
# object. Each mapping is represented as an array where the first item is the name of the
-
# entity attribute and the second item is the name of the attribute in the value object. The
-
# order in which mappings are defined determines the order in which attributes are sent to the
-
# value class constructor.
-
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
-
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
-
# mapped attributes.
-
# This defaults to +false+.
-
# * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
-
# is called to initialize the value object. The constructor is passed all of the mapped attributes,
-
# in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
-
# to instantiate a <tt>:class_name</tt> object.
-
# The default is <tt>:new</tt>.
-
# * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
-
# or a Proc that is called when a new value is assigned to the value object. The converter is
-
# passed the single value that is used in the assignment and is only called if the new value is
-
# not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
-
# can return nil to skip the assignment.
-
#
-
# Option examples:
-
# composed_of :temperature, mapping: %w(reading celsius)
-
# composed_of :balance, class_name: "Money", mapping: %w(balance amount),
-
# converter: Proc.new { |balance| balance.to_money }
-
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
-
# composed_of :gps_location
-
# composed_of :gps_location, allow_nil: true
-
# composed_of :ip_address,
-
# class_name: 'IPAddr',
-
# mapping: %w(ip to_i),
-
# constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
-
# converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
-
#
-
1
def composed_of(part_id, options = {})
-
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
-
-
name = part_id.id2name
-
class_name = options[:class_name] || name.camelize
-
mapping = options[:mapping] || [ name, name ]
-
mapping = [ mapping ] unless mapping.first.is_a?(Array)
-
allow_nil = options[:allow_nil] || false
-
constructor = options[:constructor] || :new
-
converter = options[:converter]
-
-
reader_method(name, class_name, mapping, allow_nil, constructor)
-
writer_method(name, class_name, mapping, allow_nil, converter)
-
-
create_reflection(:composed_of, part_id, nil, options, self)
-
end
-
-
1
private
-
1
def reader_method(name, class_name, mapping, allow_nil, constructor)
-
define_method(name) do
-
if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
-
attrs = mapping.collect {|pair| read_attribute(pair.first)}
-
object = constructor.respond_to?(:call) ?
-
constructor.call(*attrs) :
-
class_name.constantize.send(constructor, *attrs)
-
@aggregation_cache[name] = object
-
end
-
@aggregation_cache[name]
-
end
-
end
-
-
1
def writer_method(name, class_name, mapping, allow_nil, converter)
-
define_method("#{name}=") do |part|
-
klass = class_name.constantize
-
unless part.is_a?(klass) || converter.nil? || part.nil?
-
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
-
end
-
-
if part.nil? && allow_nil
-
mapping.each { |pair| self[pair.first] = nil }
-
@aggregation_cache[name] = nil
-
else
-
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
-
@aggregation_cache[name] = part.freeze
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class AssociationRelation < Relation
-
1
def initialize(klass, table, association)
-
1099
super(klass, table)
-
1099
@association = association
-
end
-
-
1
def proxy_association
-
@association
-
end
-
-
1
private
-
-
1
def exec_queries
-
266
super.each { |r| @association.set_inverse_instance r }
-
end
-
end
-
end
-
1
require 'active_support/core_ext/enumerable'
-
1
require 'active_support/core_ext/string/conversions'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_record/errors'
-
-
1
module ActiveRecord
-
1
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection, associated_class = nil)
-
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
-
end
-
end
-
-
1
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection)
-
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
-
end
-
end
-
-
1
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, source_reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
-
end
-
end
-
-
1
class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
-
end
-
end
-
-
1
class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, source_reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
-
end
-
end
-
-
1
class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, through_reflection)
-
super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
-
end
-
end
-
-
1
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
through_reflection = reflection.through_reflection
-
source_reflection_names = reflection.source_reflection_names
-
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
-
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
-
end
-
end
-
-
1
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
-
end
-
end
-
-
1
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
-
end
-
end
-
-
1
class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
-
end
-
end
-
-
1
class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
-
end
-
end
-
-
1
class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
-
end
-
end
-
-
1
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
-
end
-
end
-
-
1
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
-
end
-
end
-
-
# This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
-
# (has_many, has_one) when there is at least 1 child associated instance.
-
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
-
1
class DeleteRestrictionError < ActiveRecordError #:nodoc:
-
1
def initialize(name)
-
super("Cannot delete record because of dependent #{name}")
-
end
-
end
-
-
# See ActiveRecord::Associations::ClassMethods for documentation.
-
1
module Associations # :nodoc:
-
1
extend ActiveSupport::Autoload
-
1
extend ActiveSupport::Concern
-
-
# These classes will be loaded when associations are created.
-
# So there is no need to eager load them.
-
1
autoload :Association, 'active_record/associations/association'
-
1
autoload :SingularAssociation, 'active_record/associations/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/collection_association'
-
1
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
-
-
1
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
-
1
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
-
1
autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
-
1
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
-
1
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
-
1
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
-
1
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
-
1
autoload :ThroughAssociation, 'active_record/associations/through_association'
-
-
1
module Builder #:nodoc:
-
1
autoload :Association, 'active_record/associations/builder/association'
-
1
autoload :SingularAssociation, 'active_record/associations/builder/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/builder/collection_association'
-
-
1
autoload :BelongsTo, 'active_record/associations/builder/belongs_to'
-
1
autoload :HasOne, 'active_record/associations/builder/has_one'
-
1
autoload :HasMany, 'active_record/associations/builder/has_many'
-
1
autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
-
end
-
-
1
eager_autoload do
-
1
autoload :Preloader, 'active_record/associations/preloader'
-
1
autoload :JoinDependency, 'active_record/associations/join_dependency'
-
1
autoload :AssociationScope, 'active_record/associations/association_scope'
-
1
autoload :AliasTracker, 'active_record/associations/alias_tracker'
-
1
autoload :JoinHelper, 'active_record/associations/join_helper'
-
end
-
-
# Clears out the association cache.
-
1
def clear_association_cache #:nodoc:
-
4
@association_cache.clear if persisted?
-
end
-
-
# :nodoc:
-
1
attr_reader :association_cache
-
-
# Returns the association instance for the given name, instantiating it if it doesn't already exist
-
1
def association(name) #:nodoc:
-
875
association = association_instance_get(name)
-
-
875
if association.nil?
-
540
reflection = self.class.reflect_on_association(name)
-
540
association = reflection.association_class.new(self, reflection)
-
540
association_instance_set(name, association)
-
end
-
-
875
association
-
end
-
-
1
private
-
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
-
1
def association_instance_get(name)
-
5515
@association_cache[name.to_sym]
-
end
-
-
# Set the specified association instance.
-
1
def association_instance_set(name, association)
-
540
@association_cache[name] = association
-
end
-
-
# Associations are a set of macro-like class methods for tying objects together through
-
# foreign keys. They express relationships like "Project has one Project Manager"
-
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
-
# class which are specialized according to the collection or association symbol and the
-
# options hash. It works much the same way as Ruby's own <tt>attr*</tt>
-
# methods.
-
#
-
# class Project < ActiveRecord::Base
-
# belongs_to :portfolio
-
# has_one :project_manager
-
# has_many :milestones
-
# has_and_belongs_to_many :categories
-
# end
-
#
-
# The project class now has the following methods (and more) to ease the traversal and
-
# manipulation of its relationships:
-
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
-
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
-
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
-
# <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt>
-
# <tt>Project#milestones.build, Project#milestones.create</tt>
-
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
-
# <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
-
#
-
# === A word of warning
-
#
-
# Don't create associations that have the same name as instance methods of
-
# <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
-
# its model, it will override the inherited method and break things.
-
# For instance, +attributes+ and +connection+ would be bad choices for association names.
-
#
-
# == Auto-generated methods
-
#
-
# === Singular associations (one-to-one)
-
# | | belongs_to |
-
# generated methods | belongs_to | :polymorphic | has_one
-
# ----------------------------------+------------+--------------+---------
-
# other | X | X | X
-
# other=(other) | X | X | X
-
# build_other(attributes={}) | X | | X
-
# create_other(attributes={}) | X | | X
-
# create_other!(attributes={}) | X | | X
-
#
-
# ===Collection associations (one-to-many / many-to-many)
-
# | | | has_many
-
# generated methods | habtm | has_many | :through
-
# ----------------------------------+-------+----------+----------
-
# others | X | X | X
-
# others=(other,other,...) | X | X | X
-
# other_ids | X | X | X
-
# other_ids=(id,id,...) | X | X | X
-
# others<< | X | X | X
-
# others.push | X | X | X
-
# others.concat | X | X | X
-
# others.build(attributes={}) | X | X | X
-
# others.create(attributes={}) | X | X | X
-
# others.create!(attributes={}) | X | X | X
-
# others.size | X | X | X
-
# others.length | X | X | X
-
# others.count | X | X | X
-
# others.sum(*args) | X | X | X
-
# others.empty? | X | X | X
-
# others.clear | X | X | X
-
# others.delete(other,other,...) | X | X | X
-
# others.delete_all | X | X | X
-
# others.destroy(other,other,...) | X | X | X
-
# others.destroy_all | X | X | X
-
# others.find(*args) | X | X | X
-
# others.exists? | X | X | X
-
# others.distinct | X | X | X
-
# others.uniq | X | X | X
-
# others.reset | X | X | X
-
#
-
# === Overriding generated methods
-
#
-
# Association methods are generated in a module that is included into the model class,
-
# which allows you to easily override with your own methods and call the original
-
# generated method with +super+. For example:
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :owner
-
# belongs_to :old_owner
-
# def owner=(new_owner)
-
# self.old_owner = self.owner
-
# super
-
# end
-
# end
-
#
-
# If your model class is <tt>Project</tt>, the module is
-
# named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
-
# included in the model class immediately after the (anonymous) generated attributes methods
-
# module, meaning an association will override the methods for an attribute with the same name.
-
#
-
# == Cardinality and associations
-
#
-
# Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
-
# relationships between models. Each model uses an association to describe its role in
-
# the relation. The +belongs_to+ association is always used in the model that has
-
# the foreign key.
-
#
-
# === One-to-one
-
#
-
# Use +has_one+ in the base, and +belongs_to+ in the associated model.
-
#
-
# class Employee < ActiveRecord::Base
-
# has_one :office
-
# end
-
# class Office < ActiveRecord::Base
-
# belongs_to :employee # foreign key - employee_id
-
# end
-
#
-
# === One-to-many
-
#
-
# Use +has_many+ in the base, and +belongs_to+ in the associated model.
-
#
-
# class Manager < ActiveRecord::Base
-
# has_many :employees
-
# end
-
# class Employee < ActiveRecord::Base
-
# belongs_to :manager # foreign key - manager_id
-
# end
-
#
-
# === Many-to-many
-
#
-
# There are two ways to build a many-to-many relationship.
-
#
-
# The first way uses a +has_many+ association with the <tt>:through</tt> option and a join model, so
-
# there are two stages of associations.
-
#
-
# class Assignment < ActiveRecord::Base
-
# belongs_to :programmer # foreign key - programmer_id
-
# belongs_to :project # foreign key - project_id
-
# end
-
# class Programmer < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :projects, through: :assignments
-
# end
-
# class Project < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :programmers, through: :assignments
-
# end
-
#
-
# For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
-
# that has no corresponding model or primary key.
-
#
-
# class Programmer < ActiveRecord::Base
-
# has_and_belongs_to_many :projects # foreign keys in the join table
-
# end
-
# class Project < ActiveRecord::Base
-
# has_and_belongs_to_many :programmers # foreign keys in the join table
-
# end
-
#
-
# Choosing which way to build a many-to-many relationship is not always simple.
-
# If you need to work with the relationship model as its own entity,
-
# use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when
-
# you never work directly with the relationship itself.
-
#
-
# == Is it a +belongs_to+ or +has_one+ association?
-
#
-
# Both express a 1-1 relationship. The difference is mostly where to place the foreign
-
# key, which goes on the table for the class declaring the +belongs_to+ relationship.
-
#
-
# class User < ActiveRecord::Base
-
# # I reference an account.
-
# belongs_to :account
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# # One user references me.
-
# has_one :user
-
# end
-
#
-
# The tables for these classes could look something like:
-
#
-
# CREATE TABLE users (
-
# id int(11) NOT NULL auto_increment,
-
# account_id int(11) default NULL,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# CREATE TABLE accounts (
-
# id int(11) NOT NULL auto_increment,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# == Unsaved objects and associations
-
#
-
# You can manipulate objects and associations before they are saved to the database, but
-
# there is some special behavior you should be aware of, mostly involving the saving of
-
# associated objects.
-
#
-
# You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
-
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
-
# to +true+ will _always_ save the members, whereas setting it to +false+ will
-
# _never_ save the members. More details about :autosave option is available at
-
# autosave_association.rb .
-
#
-
# === One-to-one associations
-
#
-
# * Assigning an object to a +has_one+ association automatically saves that object and
-
# the object being replaced (if there is one), in order to update their foreign
-
# keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
-
# * If either of these saves fail (due to one of the objects being invalid), an
-
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
-
# cancelled.
-
# * If you wish to assign an object to a +has_one+ association without saving it,
-
# use the <tt>build_association</tt> method (documented below). The object being
-
# replaced will still be saved to update its foreign key.
-
# * Assigning an object to a +belongs_to+ association does not save the object, since
-
# the foreign key field belongs on the parent. It does not save the parent either.
-
#
-
# === Collections
-
#
-
# * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
-
# saves that object, except if the parent object (the owner of the collection) is not yet
-
# stored in the database.
-
# * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
-
# fails, then <tt>push</tt> returns +false+.
-
# * If saving fails while replacing the collection (via <tt>association=</tt>), an
-
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
-
# cancelled.
-
# * You can add an object to a collection without automatically saving it by using the
-
# <tt>collection.build</tt> method (documented below).
-
# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
-
# saved when the parent is saved.
-
#
-
# == Customizing the query
-
#
-
# Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
-
# to customize them. For example, to add a condition:
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :published_posts, -> { where published: true }, class_name: 'Post'
-
# end
-
#
-
# Inside the <tt>-> { ... }</tt> block you can use all of the usual <tt>Relation</tt> methods.
-
#
-
# === Accessing the owner object
-
#
-
# Sometimes it is useful to have access to the owner object when building the query. The owner
-
# is passed as a parameter to the block. For example, the following association would find all
-
# events that occur on the user's birthday:
-
#
-
# class User < ActiveRecord::Base
-
# has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event'
-
# end
-
#
-
# == Association callbacks
-
#
-
# Similar to the normal callbacks that hook into the life cycle of an Active Record object,
-
# you can also define callbacks that get triggered when you add an object to or remove an
-
# object from an association collection.
-
#
-
# class Project
-
# has_and_belongs_to_many :developers, after_add: :evaluate_velocity
-
#
-
# def evaluate_velocity(developer)
-
# ...
-
# end
-
# end
-
#
-
# It's possible to stack callbacks by passing them as an array. Example:
-
#
-
# class Project
-
# has_and_belongs_to_many :developers,
-
# after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
-
# end
-
#
-
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
-
#
-
# Should any of the +before_add+ callbacks throw an exception, the object does not get
-
# added to the collection. Same with the +before_remove+ callbacks; if an exception is
-
# thrown the object doesn't get removed.
-
#
-
# == Association extensions
-
#
-
# The proxy objects that control the access to associations can be extended through anonymous
-
# modules. This is especially beneficial for adding new finders, creators, and other
-
# factory-type methods that are only used as part of this association.
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
# end
-
#
-
# person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
-
# person.first_name # => "David"
-
# person.last_name # => "Heinemeier Hansson"
-
#
-
# If you need to share the same extensions between many associations, you can use a named
-
# extension module.
-
#
-
# module FindOrCreateByNameExtension
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# class Company < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# Some extensions can only be made to work with knowledge of the association's internals.
-
# Extensions can access relevant state using the following methods (where +items+ is the
-
# name of the association):
-
#
-
# * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
-
# * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
-
# * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or
-
# the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
-
#
-
# However, inside the actual extension code, you will not have access to the <tt>record</tt> as
-
# above. In this case, you can access <tt>proxy_association</tt>. For example,
-
# <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
-
# the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
-
# association extensions.
-
#
-
# == Association Join Models
-
#
-
# Has Many associations can be configured with the <tt>:through</tt> option to use an
-
# explicit join model to retrieve the data. This operates similarly to a
-
# +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
-
# callbacks, and extra attributes on the join model. Consider the following schema:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :authorships
-
# has_many :books, through: :authorships
-
# end
-
#
-
# class Authorship < ActiveRecord::Base
-
# belongs_to :author
-
# belongs_to :book
-
# end
-
#
-
# @author = Author.first
-
# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
-
# @author.books # selects all books by using the Authorship join model
-
#
-
# You can also go through a +has_many+ association on the join model:
-
#
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# has_many :invoices, through: :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base
-
# belongs_to :firm
-
# has_many :invoices
-
# end
-
#
-
# class Invoice < ActiveRecord::Base
-
# belongs_to :client
-
# end
-
#
-
# @firm = Firm.first
-
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
-
# @firm.invoices # selects all invoices by going through the Client join model
-
#
-
# Similarly you can go through a +has_one+ association on the join model:
-
#
-
# class Group < ActiveRecord::Base
-
# has_many :users
-
# has_many :avatars, through: :users
-
# end
-
#
-
# class User < ActiveRecord::Base
-
# belongs_to :group
-
# has_one :avatar
-
# end
-
#
-
# class Avatar < ActiveRecord::Base
-
# belongs_to :user
-
# end
-
#
-
# @group = Group.first
-
# @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
-
# @group.avatars # selects all avatars by going through the User join model.
-
#
-
# An important caveat with going through +has_one+ or +has_many+ associations on the
-
# join model is that these associations are *read-only*. For example, the following
-
# would not work following the previous example:
-
#
-
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
-
# @group.avatars.delete(@group.avatars.last) # so would this
-
#
-
# If you are using a +belongs_to+ on the join model, it is a good idea to set the
-
# <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
-
# works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
-
#
-
# @post = Post.first
-
# @tag = @post.tags.build name: "ruby"
-
# @tag.save
-
#
-
# The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the
-
# <tt>:inverse_of</tt> is set:
-
#
-
# class Taggable < ActiveRecord::Base
-
# belongs_to :post
-
# belongs_to :tag, inverse_of: :taggings
-
# end
-
#
-
# == Nested Associations
-
#
-
# You can actually specify *any* association with the <tt>:through</tt> option, including an
-
# association which has a <tt>:through</tt> option itself. For example:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :comments, through: :posts
-
# has_many :commenters, through: :comments
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# @author = Author.first
-
# @author.commenters # => People who commented on posts written by the author
-
#
-
# An equivalent way of setting up this association this would be:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :commenters, through: :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# has_many :commenters, through: :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# When using nested association, you will not be able to modify the association because there
-
# is not enough information to know what modification to make. For example, if you tried to
-
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
-
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
-
#
-
# == Polymorphic Associations
-
#
-
# Polymorphic associations on models are not restricted on what types of models they
-
# can be associated with. Rather, they specify an interface that a +has_many+ association
-
# must adhere to.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, polymorphic: true
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
-
# end
-
#
-
# @asset.attachable = @post
-
#
-
# This works by using a type column in addition to a foreign key to specify the associated
-
# record. In the Asset example, you'd need an +attachable_id+ integer column and an
-
# +attachable_type+ string column.
-
#
-
# Using polymorphic associations in combination with single table inheritance (STI) is
-
# a little tricky. In order for the associations to work as expected, ensure that you
-
# store the base model for the STI models in the type column of the polymorphic
-
# association. To continue with the asset example above, suppose there are guest posts
-
# and member posts that use the posts table for STI. In this case, there must be a +type+
-
# column in the posts table.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, polymorphic: true
-
#
-
# def attachable_type=(klass)
-
# super(klass.to_s.classify.constantize.base_class.to_s)
-
# end
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# # because we store "Post" in attachable_type now dependent: :destroy will work
-
# has_many :assets, as: :attachable, dependent: :destroy
-
# end
-
#
-
# class GuestPost < Post
-
# end
-
#
-
# class MemberPost < Post
-
# end
-
#
-
# == Caching
-
#
-
# All of the methods are built on a simple caching principle that will keep the result
-
# of the last query around unless specifically instructed not to. The cache is even
-
# shared across methods to make it even cheaper to use the macro-added methods without
-
# worrying too much about performance at the first go.
-
#
-
# project.milestones # fetches milestones from the database
-
# project.milestones.size # uses the milestone cache
-
# project.milestones.empty? # uses the milestone cache
-
# project.milestones(true).size # fetches milestones from the database
-
# project.milestones # uses the milestone cache
-
#
-
# == Eager loading of associations
-
#
-
# Eager loading is a way to find objects of a certain class and a number of named associations.
-
# This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100
-
# posts that each need to display their author triggers 101 database queries. Through the
-
# use of eager loading, the 101 queries can be reduced to 2.
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# has_many :comments
-
# end
-
#
-
# Consider the following loop using the class above:
-
#
-
# Post.all.each do |post|
-
# puts "Post: " + post.title
-
# puts "Written by: " + post.author.name
-
# puts "Last comment on: " + post.comments.first.created_on
-
# end
-
#
-
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's
-
# first just optimize it for retrieving the author:
-
#
-
# Post.includes(:author).each do |post|
-
#
-
# This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
-
# symbol. After loading the posts, find will collect the +author_id+ from each one and load
-
# all the referenced authors with one query. Doing so will cut down the number of queries
-
# from 201 to 102.
-
#
-
# We can improve upon the situation further by referencing both associations in the finder with:
-
#
-
# Post.includes(:author, :comments).each do |post|
-
#
-
# This will load all comments with a single query. This reduces the total number of queries
-
# to 3. More generally the number of queries will be 1 plus the number of associations
-
# named (except if some of the associations are polymorphic +belongs_to+ - see below).
-
#
-
# To include a deep hierarchy of associations, use a hash:
-
#
-
# Post.includes(:author, {comments: {author: :gravatar}}).each do |post|
-
#
-
# That'll grab not only all the comments but all their authors and gravatar pictures.
-
# You can mix and match symbols, arrays and hashes in any combination to describe the
-
# associations you want to load.
-
#
-
# All of this power shouldn't fool you into thinking that you can pull out huge amounts
-
# of data with no performance penalty just because you've reduced the number of queries.
-
# The database still needs to send all the data to Active Record and it still needs to
-
# be processed. So it's no catch-all for performance problems, but it's a great way to
-
# cut down on the number of queries in a situation as the one described above.
-
#
-
# Since only one table is loaded at a time, conditions or orders cannot reference tables
-
# other than the main one. If this is the case Active Record falls back to the previously
-
# used LEFT OUTER JOIN based strategy. For example
-
#
-
# Post.includes([:author, :comments]).where(['comments.approved = ?', true])
-
#
-
# This will result in a single SQL query with joins along the lines of:
-
# <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
-
# <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
-
# like this can have unintended consequences.
-
# In the above example posts with no approved comments are not returned at all, because
-
# the conditions apply to the SQL statement as a whole and not just to the association.
-
# You must disambiguate column references for this fallback to happen, for example
-
# <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
-
#
-
# If you do want eager load only some members of an association it is usually more natural
-
# to include an association which has conditions defined on it:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :approved_comments, -> { where approved: true }, class_name: 'Comment'
-
# end
-
#
-
# Post.includes(:approved_comments)
-
#
-
# This will load posts and eager load the +approved_comments+ association, which contains
-
# only those comments that have been approved.
-
#
-
# If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
-
# returning all the associated objects:
-
#
-
# class Picture < ActiveRecord::Base
-
# has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
-
# end
-
#
-
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
-
#
-
# Eager loading is supported with polymorphic associations.
-
#
-
# class Address < ActiveRecord::Base
-
# belongs_to :addressable, polymorphic: true
-
# end
-
#
-
# A call that tries to eager load the addressable model
-
#
-
# Address.includes(:addressable)
-
#
-
# This will execute one query to load the addresses and load the addressables with one
-
# query per addressable type.
-
# For example if all the addressables are either of class Person or Company then a total
-
# of 3 queries will be executed. The list of addressable types to load is determined on
-
# the back of the addresses loaded. This is not supported if Active Record has to fallback
-
# to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
-
# The reason is that the parent model's type is a column value so its corresponding table
-
# name cannot be put in the +FROM+/+JOIN+ clauses of that query.
-
#
-
# == Table Aliasing
-
#
-
# Active Record uses table aliasing in the case that a table is referenced multiple times
-
# in a join. If a table is referenced only once, the standard table name is used. The
-
# second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
-
# Indexes are appended for any more successive uses of the table name.
-
#
-
# Post.joins(:comments)
-
# # => SELECT ... FROM posts INNER JOIN comments ON ...
-
# Post.joins(:special_comments) # STI
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
-
# Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
-
#
-
# Acts as tree example:
-
#
-
# TreeMixin.joins(:children)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# TreeMixin.joins(children: :parent)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# TreeMixin.joins(children: {parent: :children})
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# INNER JOIN mixins childrens_mixins_2
-
#
-
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
-
#
-
# Post.joins(:categories)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# Post.joins(categories: :posts)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# Post.joins(categories: {posts: :categories})
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
-
#
-
# If you wish to specify your own custom joins using <tt>joins</tt> method, those table
-
# names will take precedence over the eager associations:
-
#
-
# Post.joins(:comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
-
# Post.joins(:comments, :special_comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
-
# INNER JOIN comments special_comments_posts ...
-
# INNER JOIN comments ...
-
#
-
# Table aliases are automatically truncated according to the maximum length of table identifiers
-
# according to the specific database.
-
#
-
# == Modules
-
#
-
# By default, associations will look for objects within the current module scope. Consider:
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base; end
-
# end
-
# end
-
#
-
# When <tt>Firm#clients</tt> is called, it will in turn call
-
# <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
-
# If you want to associate with a class in another module scope, this can be done by
-
# specifying the complete class name.
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base; end
-
# end
-
#
-
# module Billing
-
# class Account < ActiveRecord::Base
-
# belongs_to :firm, class_name: "MyApplication::Business::Firm"
-
# end
-
# end
-
# end
-
#
-
# == Bi-directional associations
-
#
-
# When you specify an association there is usually an association on the associated model
-
# that specifies the same relationship in reverse. For example, with the following models:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps
-
# has_one :evil_wizard
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
-
# the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
-
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
-
# Active Record doesn't know anything about these inverse relationships and so no object
-
# loading optimization is possible. For example:
-
#
-
# d = Dungeon.first
-
# t = d.traps.first
-
# d.level == t.dungeon.level # => true
-
# d.level = 10
-
# d.level == t.dungeon.level # => false
-
#
-
# The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
-
# the same object data from the database, but are actually different in-memory copies
-
# of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
-
# Active Record about inverse relationships and it will optimise object loading. For
-
# example, if we changed our model definitions to:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps, inverse_of: :dungeon
-
# has_one :evil_wizard, inverse_of: :dungeon
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon, inverse_of: :traps
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon, inverse_of: :evil_wizard
-
# end
-
#
-
# Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
-
# in-memory instance and our final <tt>d.level == t.dungeon.level</tt> will return +true+.
-
#
-
# There are limitations to <tt>:inverse_of</tt> support:
-
#
-
# * does not work with <tt>:through</tt> associations.
-
# * does not work with <tt>:polymorphic</tt> associations.
-
# * for +belongs_to+ associations +has_many+ inverse associations are ignored.
-
#
-
# == Deleting from associations
-
#
-
# === Dependent associations
-
#
-
# +has_many+, +has_one+ and +belongs_to+ associations support the <tt>:dependent</tt> option.
-
# This allows you to specify that associated records should be deleted when the owner is
-
# deleted.
-
#
-
# For example:
-
#
-
# class Author
-
# has_many :posts, dependent: :destroy
-
# end
-
# Author.find(1).destroy # => Will destroy all of the author's posts, too
-
#
-
# The <tt>:dependent</tt> option can have different values which specify how the deletion
-
# is done. For more information, see the documentation for this option on the different
-
# specific association types. When no option is given, the behavior is to do nothing
-
# with the associated records when destroying a record.
-
#
-
# Note that <tt>:dependent</tt> is implemented using Rails' callback
-
# system, which works by processing callbacks in order. Therefore, other
-
# callbacks declared either before or after the <tt>:dependent</tt> option
-
# can affect what it does.
-
#
-
# === Delete or destroy?
-
#
-
# +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
-
# <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
-
#
-
# For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
-
# cause the records in the join table to be removed.
-
#
-
# For +has_many+, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
-
# record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
-
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
-
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
-
# The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
-
# +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
-
# the join records, without running their callbacks).
-
#
-
# There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
-
# it returns the association rather than the records which have been deleted.
-
#
-
# === What gets deleted?
-
#
-
# There is a potential pitfall here: +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>
-
# associations have records in join tables, as well as the associated records. So when we
-
# call one of these deletion methods, what exactly should be deleted?
-
#
-
# The answer is that it is assumed that deletion on an association is about removing the
-
# <i>link</i> between the owner and the associated object(s), rather than necessarily the
-
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
-
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
-
#
-
# This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
-
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
-
# to be removed from the database.
-
#
-
# However, there are examples where this strategy doesn't make sense. For example, suppose
-
# a person has many projects, and each project has many tasks. If we deleted one of a person's
-
# tasks, we would probably not want the project to be deleted. In this scenario, the delete method
-
# won't actually work: it can only be used if the association on the join model is a
-
# +belongs_to+. In other situations you are expected to perform operations directly on
-
# either the associated records or the <tt>:through</tt> association.
-
#
-
# With a regular +has_many+ there is no distinction between the "associated records"
-
# and the "link", so there is only one choice for what gets deleted.
-
#
-
# With +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>, if you want to delete the
-
# associated records themselves, you can always do something along the lines of
-
# <tt>person.tasks.each(&:destroy)</tt>.
-
#
-
# == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
-
#
-
# If you attempt to assign an object to an association that doesn't match the inferred
-
# or specified <tt>:class_name</tt>, you'll get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
-
#
-
# == Options
-
#
-
# All of the association macros can be specialized through options. This makes cases
-
# more complex than the simple and guessable ones possible.
-
1
module ClassMethods
-
# Specifies a one-to-many association. The following methods for retrieval and query of
-
# collections of associated objects will be added:
-
#
-
# [collection(force_reload = false)]
-
# Returns an array of all the associated objects.
-
# An empty array is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
-
# Note that this operation instantly fires update sql without waiting for the save or update call on the
-
# parent object, unless the parent object is a new record.
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
-
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
-
# and deleted if they're associated with <tt>dependent: :delete_all</tt>.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are deleted (rather than
-
# nullified) by default, but you can specify <tt>dependent: :destroy</tt> or
-
# <tt>dependent: :nullify</tt> to override this.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running <tt>destroy</tt> on
-
# each record, regardless of any dependent option, ensuring callbacks are run.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are destroyed
-
# instead, not the objects themselves.
-
# [collection=objects]
-
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
-
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
-
# direct.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids
-
# [collection_singular_ids=ids]
-
# Replace the collection with the objects identified by the primary keys in +ids+. This
-
# method loads the models and calls <tt>collection=</tt>. See above.
-
# [collection.clear]
-
# Removes every object from the collection. This destroys the associated objects if they
-
# are associated with <tt>dependent: :destroy</tt>, deletes them directly from the
-
# database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
-
# If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
-
# Join models are directly deleted.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(...)]
-
# Finds an associated object according to the same rules as ActiveRecord::Base.find.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as ActiveRecord::Base.exists?.
-
# [collection.build(attributes = {}, ...)]
-
# Returns one or more new objects of the collection type that have been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but have not yet
-
# been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that has already
-
# been saved (if it passed the validation). *Note*: This only works if the base model
-
# already exists in the DB, not if it is a new (unsaved) record!
-
# [collection.create!(attributes = {})]
-
# Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
-
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
-
#
-
# === Example
-
#
-
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
-
# * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
-
# * <tt>Firm#clients<<</tt>
-
# * <tt>Firm#clients.delete</tt>
-
# * <tt>Firm#clients.destroy</tt>
-
# * <tt>Firm#clients=</tt>
-
# * <tt>Firm#client_ids</tt>
-
# * <tt>Firm#client_ids=</tt>
-
# * <tt>Firm#clients.clear</tt>
-
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
-
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
-
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>)
-
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
-
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
-
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
-
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
-
# The declaration can also include an options hash to specialize the behavior of the association.
-
#
-
# === Options
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_many :products</tt> will by default be linked
-
# to the Product class, but if the real class name is SpecialProduct, you'll have to
-
# specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
-
# association will use "person_id" as the default <tt>:foreign_key</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key used for the association. By default this is +id+.
-
# [:dependent]
-
# Controls what happens to the associated objects when
-
# their owner is destroyed. Note that these are implemented as
-
# callbacks, and Rails executes callbacks in order. Therefore, other
-
# similar callbacks may affect the :dependent behavior, and the
-
# :dependent behavior may affect other callbacks.
-
#
-
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
-
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
-
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
-
#
-
# If using with the <tt>:through</tt> option, the association on the join model must be
-
# a +belongs_to+, and the records which get deleted are the join records, rather than
-
# the associated records.
-
# [:counter_cache]
-
# This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
-
# when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association.
-
# [:as]
-
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
-
# [:through]
-
# Specifies an association through which to perform the query. This can be any other type
-
# of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection.
-
#
-
# If the association on the join model is a +belongs_to+, the collection can be modified
-
# and the records on the <tt>:through</tt> model will be automatically created and removed
-
# as appropriate. Otherwise, the collection is read-only, so you should manipulate the
-
# <tt>:through</tt> association directly.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option on the source association on the
-
# join model. This allows associated records to be built which will automatically create
-
# the appropriate join model records when they are saved. (See the 'Association Join Models'
-
# section above.)
-
# [:source]
-
# Specifies the source association name used by <tt>has_many :through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or
-
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
-
# association is a polymorphic +belongs_to+.
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. true by default.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records. This option is implemented as a
-
# before_save callback. Because callbacks are run in the order they are defined, associated objects
-
# may need to be explicitly saved in any user-defined before_save callbacks.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
-
# that is the inverse of this <tt>has_many</tt> association. Does not work in combination
-
# with <tt>:through</tt> or <tt>:as</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
#
-
# Option examples:
-
# has_many :comments, -> { order "posted_on" }
-
# has_many :comments, -> { includes :author }
-
# has_many :people, -> { where("deleted = 0").order("name") }, class_name: "Person"
-
# has_many :tracks, -> { order "position" }, dependent: :destroy
-
# has_many :comments, dependent: :nullify
-
# has_many :tags, as: :taggable
-
# has_many :reports, -> { readonly }
-
# has_many :subscribers, through: :subscriptions, source: :user
-
1
def has_many(name, scope = nil, options = {}, &extension)
-
8
Builder::HasMany.build(self, name, scope, options, &extension)
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if the other class contains the foreign key. If the current class contains the foreign key,
-
# then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use has_one and when to use belongs_to.
-
#
-
# The following methods for retrieval and query of a single associated object will be added:
-
#
-
# [association(force_reload = false)]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
-
# and saves the associate object.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not
-
# yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# (+association+ is replaced with the symbol passed as the first argument, so
-
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
-
#
-
# === Example
-
#
-
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
-
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>)
-
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
-
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
-
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
-
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
-
#
-
# === Options
-
#
-
# The declaration can also include an options hash to specialize the behavior of the association.
-
#
-
# Options are:
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:dependent]
-
# Controls what happens to the associated object when
-
# its owner is destroyed:
-
#
-
# * <tt>:destroy</tt> causes the associated object to also be destroyed
-
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
-
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
-
# will use "person_id" as the default <tt>:foreign_key</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key used for the association. By default this is +id+.
-
# [:as]
-
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
-
# [:through]
-
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
-
# or <tt>belongs_to</tt> association on the join model.
-
# [:source]
-
# Specifies the source association name used by <tt>has_one :through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_one :favorite, through: :favorites</tt> will look for a
-
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
-
# association is a polymorphic +belongs_to+.
-
# [:validate]
-
# If +false+, don't validate the associated object when saving the parent object. +false+ by default.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
-
# that is the inverse of this <tt>has_one</tt> association. Does not work in combination
-
# with <tt>:through</tt> or <tt>:as</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
#
-
# Option examples:
-
# has_one :credit_card, dependent: :destroy # destroys the associated credit card
-
# has_one :credit_card, dependent: :nullify # updates the associated records foreign
-
# # key value to NULL rather than destroying it
-
# has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
-
# has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
-
# has_one :attachment, as: :attachable
-
# has_one :boss, readonly: :true
-
# has_one :club, through: :membership
-
# has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
-
1
def has_one(name, scope = nil, options = {})
-
Builder::HasOne.build(self, name, scope, options)
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if this class contains the foreign key. If the other class contains the foreign key,
-
# then you should use +has_one+ instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use +has_one+ and when to use +belongs_to+.
-
#
-
# Methods will be added for retrieval and query for a single associated object, for which
-
# this object holds an id:
-
#
-
# [association(force_reload = false)]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# (+association+ is replaced with the symbol passed as the first argument, so
-
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
-
#
-
# === Example
-
#
-
# A Post class declares <tt>belongs_to :author</tt>, which will add:
-
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
-
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
-
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
-
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
-
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
-
# The declaration can also include an options hash to specialize the behavior of the association.
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
-
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
-
# <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
-
# of "favorite_person_id".
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the association with a "_type"
-
# suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt>
-
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key of associated object used for the association.
-
# By default this is id.
-
# [:dependent]
-
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
-
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
-
# This option should not be specified when <tt>belongs_to</tt> is used in conjunction with
-
# a <tt>has_many</tt> relationship on another class because of the potential to leave
-
# orphaned records behind.
-
# [:counter_cache]
-
# Caches the number of belonging objects on the associate class through the use of +increment_counter+
-
# and +decrement_counter+. The counter cache is incremented when an object of this
-
# class is created and decremented when it's destroyed. This requires that a column
-
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
-
# is used on the associate class (such as a Post class) - that is the migration for
-
# <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
-
# return the count cached, see note below). You can also specify a custom counter
-
# cache column by providing a column name instead of a +true+/+false+ value to this
-
# option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
-
# Note: Specifying a counter cache will add it to that model's list of readonly attributes
-
# using +attr_readonly+.
-
# [:polymorphic]
-
# Specify this association is a polymorphic association by passing +true+.
-
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
-
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:touch]
-
# If true, the associated object will be touched (the updated_at/on attributes set to now)
-
# when this record is either saved or destroyed. If you specify a symbol, that attribute
-
# will be updated with the current time in addition to the updated_at/on attribute.
-
# [:inverse_of]
-
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
-
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
-
# combination with the <tt>:polymorphic</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
#
-
# Option examples:
-
# belongs_to :firm, foreign_key: "client_of"
-
# belongs_to :person, primary_key: "name", foreign_key: "person_name"
-
# belongs_to :author, class_name: "Person", foreign_key: "author_id"
-
# belongs_to :valid_coupon, ->(o) { where "discounts > #{o.payments_count}" },
-
# class_name: "Coupon", foreign_key: "coupon_id"
-
# belongs_to :attachable, polymorphic: true
-
# belongs_to :project, readonly: true
-
# belongs_to :post, counter_cache: true
-
# belongs_to :company, touch: true
-
# belongs_to :company, touch: :employees_last_updated_at
-
1
def belongs_to(name, scope = nil, options = {})
-
6
Builder::BelongsTo.build(self, name, scope, options)
-
end
-
-
# Specifies a many-to-many relationship with another class. This associates two classes via an
-
# intermediate join table. Unless the join table is explicitly specified as an option, it is
-
# guessed using the lexical order of the class names. So a join between Developer and Project
-
# will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
-
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
-
# means that if the strings are of different lengths, and the strings are equal when compared
-
# up to the shortest length, then the longer string is considered of higher
-
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
-
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
-
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
-
# custom <tt>:join_table</tt> option if you need to.
-
# If your tables share a common prefix, it will only appear once at the beginning. For example,
-
# the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products".
-
#
-
# The join table should not have a primary key or a model associated with it. You must manually generate the
-
# join table with a migration such as this:
-
#
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
-
# def change
-
# create_table :developers_projects, id: false do |t|
-
# t.integer :developer_id
-
# t.integer :project_id
-
# end
-
# end
-
# end
-
#
-
# It's also a good idea to add indexes to each of those columns to speed up the joins process.
-
# However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
-
# uses one index per table during the lookup.
-
#
-
# Adds the following methods for retrieval and query:
-
#
-
# [collection(force_reload = false)]
-
# Returns an array of all the associated objects.
-
# An empty array is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by creating associations in the join table
-
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
-
# Note that this operation instantly fires update sql without waiting for the save or update call on the
-
# parent object, unless the parent object is a new record.
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by removing their associations from the join table.
-
# This does not destroy the objects.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option.
-
# This does not destroy the objects.
-
# [collection=objects]
-
# Replaces the collection's content by deleting and adding objects as appropriate.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids.
-
# [collection_singular_ids=ids]
-
# Replace the collection by the objects identified by the primary keys in +ids+.
-
# [collection.clear]
-
# Removes every object from the collection. This does not destroy the objects.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(id)]
-
# Finds an associated object responding to the +id+ and that
-
# meets the condition that it has to be associated with this object.
-
# Uses the same rules as ActiveRecord::Base.find.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as ActiveRecord::Base.exists?.
-
# [collection.build(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through the join table, and that has already been
-
# saved (if it passed the validation).
-
#
-
# (+collection+ is replaced with the symbol passed as the first argument, so
-
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
-
#
-
# === Example
-
#
-
# A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
-
# * <tt>Developer#projects</tt>
-
# * <tt>Developer#projects<<</tt>
-
# * <tt>Developer#projects.delete</tt>
-
# * <tt>Developer#projects.destroy</tt>
-
# * <tt>Developer#projects=</tt>
-
# * <tt>Developer#project_ids</tt>
-
# * <tt>Developer#project_ids=</tt>
-
# * <tt>Developer#projects.clear</tt>
-
# * <tt>Developer#projects.empty?</tt>
-
# * <tt>Developer#projects.size</tt>
-
# * <tt>Developer#projects.find(id)</tt>
-
# * <tt>Developer#projects.exists?(...)</tt>
-
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
-
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
-
# The declaration may include an options hash to specialize the behavior of the association.
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
-
# Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
-
# [:join_table]
-
# Specify the name of the join table if the default based on lexical order isn't what you want.
-
# <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method
-
# MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes
-
# a +has_and_belongs_to_many+ association to Project will use "person_id" as the
-
# default <tt>:foreign_key</tt>.
-
# [:association_foreign_key]
-
# Specify the foreign key used for the association on the receiving side of the association.
-
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
-
# So if a Person class makes a +has_and_belongs_to_many+ association to Project,
-
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
-
# [:readonly]
-
# If true, all the associated objects are readonly through the association.
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
#
-
# Option examples:
-
# has_and_belongs_to_many :projects
-
# has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
-
# has_and_belongs_to_many :nations, class_name: "Country"
-
# has_and_belongs_to_many :categories, join_table: "prods_cats"
-
# has_and_belongs_to_many :categories, -> { readonly }
-
1
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
-
Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/conversions'
-
-
1
module ActiveRecord
-
1
module Associations
-
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
-
# ActiveRecord::Associations::ThroughAssociationScope
-
1
class AliasTracker # :nodoc:
-
1
attr_reader :aliases, :table_joins, :connection
-
-
# table_joins is an array of arel joins which might conflict with the aliases we assign here
-
1
def initialize(connection = Base.connection, table_joins = [])
-
3273
@aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
-
1524
@table_joins = table_joins
-
1524
@connection = connection
-
end
-
-
1
def aliased_table_for(table_name, aliased_name = nil)
-
721
table_alias = aliased_name_for(table_name, aliased_name)
-
-
721
if table_alias == table_name
-
721
Arel::Table.new(table_name)
-
else
-
Arel::Table.new(table_name).alias(table_alias)
-
end
-
end
-
-
1
def aliased_name_for(table_name, aliased_name = nil)
-
1749
aliased_name ||= table_name
-
-
1749
if aliases[table_name].zero?
-
# If it's zero, we can have our table_name
-
1749
aliases[table_name] = 1
-
1749
table_name
-
else
-
# Otherwise, we need to use an alias
-
aliased_name = connection.table_alias_for(aliased_name)
-
-
# Update the count
-
aliases[aliased_name] += 1
-
-
if aliases[aliased_name] > 1
-
"#{truncate(aliased_name)}_#{aliases[aliased_name]}"
-
else
-
aliased_name
-
end
-
end
-
end
-
-
1
private
-
-
1
def initial_count_for(name)
-
1749
return 0 if Arel::Table === table_joins
-
-
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
-
1749
quoted_name = connection.quote_table_name(name).downcase
-
-
1749
counts = table_joins.map do |join|
-
474
if join.is_a?(Arel::Nodes::StringJoin)
-
# Table names + table aliases
-
join.left.downcase.scan(
-
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
-
).size
-
else
-
474
join.left.table_name == name ? 1 : 0
-
end
-
end
-
-
1749
counts.sum
-
end
-
-
1
def truncate(name)
-
name.slice(0, connection.table_alias_length - 2)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/wrap'
-
-
1
module ActiveRecord
-
1
module Associations
-
# = Active Record Associations
-
#
-
# This is the root class of all associations ('+ Foo' signifies an included module Foo):
-
#
-
# Association
-
# SingularAssociation
-
# HasOneAssociation
-
# HasOneThroughAssociation + ThroughAssociation
-
# BelongsToAssociation
-
# BelongsToPolymorphicAssociation
-
# CollectionAssociation
-
# HasAndBelongsToManyAssociation
-
# HasManyAssociation
-
# HasManyThroughAssociation + ThroughAssociation
-
1
class Association #:nodoc:
-
1
attr_reader :owner, :target, :reflection
-
1
attr_accessor :inversed
-
-
1
delegate :options, :to => :reflection
-
-
1
def initialize(owner, reflection)
-
540
reflection.check_validity!
-
-
540
@owner, @reflection = owner, reflection
-
-
540
reset
-
540
reset_scope
-
end
-
-
# Returns the name of the table of the associated class:
-
#
-
# post.comments.aliased_table_name # => "comments"
-
#
-
1
def aliased_table_name
-
klass.table_name
-
end
-
-
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
-
1
def reset
-
625
@loaded = false
-
625
@target = nil
-
625
@stale_state = nil
-
625
@inversed = false
-
end
-
-
# Reloads the \target and returns +self+ on success.
-
1
def reload
-
73
reset
-
73
reset_scope
-
73
load_target
-
73
self unless target.nil?
-
end
-
-
# Has the \target been already \loaded?
-
1
def loaded?
-
1348
@loaded
-
end
-
-
# Asserts the \target has been loaded setting the \loaded flag to +true+.
-
1
def loaded!
-
201
@loaded = true
-
201
@stale_state = stale_state
-
201
@inversed = false
-
end
-
-
# The target is stale if the target no longer points to the record(s) that the
-
# relevant foreign_key(s) refers to. If stale, the association accessor method
-
# on the owner will reload the target. It's up to subclasses to implement the
-
# stale_state method if relevant.
-
#
-
# Note that if the target has not been loaded, it is not considered stale.
-
1
def stale_target?
-
790
!inversed && loaded? && @stale_state != stale_state
-
end
-
-
# Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
-
1
def target=(target)
-
44
@target = target
-
44
loaded!
-
end
-
-
1
def scope
-
1099
target_scope.merge(association_scope)
-
end
-
-
1
def scoped
-
ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead."
-
scope
-
end
-
-
# The scope for this association.
-
#
-
# Note that the association_scope is merged into the target_scope only when the
-
# scope method is called. This is because at that point the call may be surrounded
-
# by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
-
# actually gets built.
-
1
def association_scope
-
1554
if klass
-
1554
@association_scope ||= AssociationScope.new(self).scope
-
end
-
end
-
-
1
def reset_scope
-
613
@association_scope = nil
-
end
-
-
# Set the inverse association, if possible
-
1
def set_inverse_instance(record)
-
576
if record && invertible_for?(record)
-
inverse = record.association(inverse_reflection_for(record).name)
-
inverse.target = owner
-
inverse.inversed = true
-
end
-
end
-
-
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
-
# polymorphic_type field on the owner.
-
1
def klass
-
7335
reflection.klass
-
end
-
-
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
-
# through association's scope)
-
1
def target_scope
-
1099
all = klass.all
-
1099
scope = AssociationRelation.new(klass, klass.arel_table, self)
-
1099
scope.merge! all
-
1099
scope.default_scoped = all.default_scoped?
-
1099
scope
-
end
-
-
# Loads the \target if needed and returns it.
-
#
-
# This method is abstract in the sense that it relies on +find_target+,
-
# which is expected to be provided by descendants.
-
#
-
# If the \target is already \loaded it is just returned. Thus, you can call
-
# +load_target+ unconditionally to get the \target.
-
#
-
# ActiveRecord::RecordNotFound is rescued within the method, and it is
-
# not reraised. The proxy is \reset and +nil+ is the return value.
-
1
def load_target
-
117
@target = find_target if (@stale_state && stale_target?) || find_target?
-
-
117
loaded! unless loaded?
-
117
target
-
rescue ActiveRecord::RecordNotFound
-
reset
-
end
-
-
1
def interpolate(sql, record = nil)
-
if sql.respond_to?(:to_proc)
-
owner.instance_exec(record, &sql)
-
else
-
sql
-
end
-
end
-
-
# We can't dump @reflection since it contains the scope proc
-
1
def marshal_dump
-
ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
-
[@reflection.name, ivars]
-
end
-
-
1
def marshal_load(data)
-
reflection_name, ivars = data
-
ivars.each { |name, val| instance_variable_set(name, val) }
-
@reflection = @owner.class.reflect_on_association(reflection_name)
-
end
-
-
1
def initialize_attributes(record) #:nodoc:
-
141
skip_assign = [reflection.foreign_key, reflection.type].compact
-
141
attributes = create_scope.except(*(record.changed - skip_assign))
-
141
record.assign_attributes(attributes)
-
141
set_inverse_instance(record)
-
end
-
-
1
private
-
-
1
def find_target?
-
28
!loaded? && (!owner.new_record? || foreign_key_present?) && klass
-
end
-
-
1
def creation_attributes
-
39
attributes = {}
-
-
39
if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through]
-
39
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
-
-
39
if reflection.options[:as]
-
attributes[reflection.type] = owner.class.base_class.name
-
end
-
end
-
-
39
attributes
-
end
-
-
# Sets the owner attributes on the given record
-
1
def set_owner_attributes(record)
-
78
creation_attributes.each { |key, value| record[key] = value }
-
end
-
-
# Should be true if there is a foreign key present on the owner which
-
# references the target. This is used to determine whether we can load
-
# the target if the owner is currently a new record (and therefore
-
# without a key).
-
#
-
# Currently implemented by belongs_to (vanilla and polymorphic) and
-
# has_one/has_many :through associations which go through a belongs_to
-
1
def foreign_key_present?
-
false
-
end
-
-
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
-
# the kind of the class of the associated objects. Meant to be used as
-
# a sanity check when you are about to assign an associated record.
-
1
def raise_on_type_mismatch!(record)
-
46
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
-
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
-
raise ActiveRecord::AssociationTypeMismatch, message
-
end
-
end
-
-
# Can be redefined by subclasses, notably polymorphic belongs_to
-
# The record parameter is necessary to support polymorphic inverses as we must check for
-
# the association in the specific class of the record.
-
1
def inverse_reflection_for(record)
-
570
reflection.inverse_of
-
end
-
-
# Returns true if inverse association on the given record needs to be set.
-
# This method is redefined by subclasses.
-
1
def invertible_for?(record)
-
380
foreign_key_for?(record) && inverse_reflection_for(record)
-
end
-
-
# Returns true if record contains the foreign_key
-
1
def foreign_key_for?(record)
-
380
record.has_attribute?(reflection.foreign_key)
-
end
-
-
# This should be implemented to return the values of the relevant key(s) on the owner,
-
# so that when stale_state is different from the value stored on the last find_target,
-
# the target is stale.
-
#
-
# This is only relevant to certain associations, which is why it returns nil by default.
-
1
def stale_state
-
end
-
-
1
def build_record(attributes)
-
141
reflection.build_association(attributes) do |record|
-
141
initialize_attributes(record)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class AssociationScope #:nodoc:
-
1
include JoinHelper
-
-
1
attr_reader :association, :alias_tracker
-
-
1
delegate :klass, :owner, :reflection, :interpolate, :to => :association
-
1
delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
-
-
1
def initialize(association)
-
496
@association = association
-
496
@alias_tracker = AliasTracker.new klass.connection
-
end
-
-
1
def scope
-
496
scope = klass.unscoped
-
496
scope.extending! Array(options[:extend])
-
496
add_constraints(scope)
-
end
-
-
1
private
-
-
1
def column_for(table_name, column_name)
-
496
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
-
496
columns[column_name]
-
end
-
-
1
def bind_value(scope, column, value)
-
496
substitute = alias_tracker.connection.substitute_at(
-
column, scope.bind_values.length)
-
496
scope.bind_values += [[column, value]]
-
496
substitute
-
end
-
-
1
def bind(scope, table_name, column_name, value)
-
496
column = column_for table_name, column_name
-
496
bind_value scope, column, value
-
end
-
-
1
def add_constraints(scope)
-
496
tables = construct_tables
-
-
496
chain.each_with_index do |reflection, i|
-
721
table, foreign_table = tables.shift, tables.first
-
-
721
if reflection.source_macro == :has_and_belongs_to_many
-
join_table = tables.shift
-
-
scope = scope.joins(join(
-
join_table,
-
table[reflection.association_primary_key].
-
eq(join_table[reflection.association_foreign_key])
-
))
-
-
table, foreign_table = join_table, tables.first
-
end
-
-
721
if reflection.source_macro == :belongs_to
-
298
if reflection.options[:polymorphic]
-
key = reflection.association_primary_key(self.klass)
-
else
-
298
key = reflection.association_primary_key
-
end
-
-
298
foreign_key = reflection.foreign_key
-
else
-
423
key = reflection.foreign_key
-
423
foreign_key = reflection.active_record_primary_key
-
end
-
-
721
if reflection == chain.last
-
496
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
-
496
scope = scope.where(table[key].eq(bind_val))
-
-
496
if reflection.type
-
value = owner.class.base_class.name
-
bind_val = bind scope, table.table_name, reflection.type.to_s, value
-
scope = scope.where(table[reflection.type].eq(bind_val))
-
end
-
else
-
225
constraint = table[key].eq(foreign_table[foreign_key])
-
-
225
if reflection.type
-
type = chain[i + 1].klass.base_class.name
-
constraint = constraint.and(table[reflection.type].eq(type))
-
end
-
-
225
scope = scope.joins(join(foreign_table, constraint))
-
end
-
-
721
is_first_chain = i == 0
-
721
klass = is_first_chain ? self.klass : reflection.klass
-
-
# Exclude the scope of the association itself, because that
-
# was already merged in the #scope method.
-
721
scope_chain[i].each do |scope_chain_item|
-
item = eval_scope(klass, scope_chain_item)
-
-
if scope_chain_item == self.reflection.scope
-
scope.merge! item.except(:where, :includes)
-
end
-
-
if is_first_chain
-
scope.includes! item.includes_values
-
end
-
-
scope.where_values += item.where_values
-
scope.order_values |= item.order_values
-
end
-
end
-
-
496
scope
-
end
-
-
1
def alias_suffix
-
721
reflection.name
-
end
-
-
1
def table_name_for(reflection)
-
721
if reflection == self.reflection
-
# If this is a polymorphic belongs_to, we want to get the klass from the
-
# association because it depends on the polymorphic_type attribute of
-
# the owner
-
496
klass.table_name
-
else
-
225
reflection.table_name
-
end
-
end
-
-
1
def eval_scope(klass, scope)
-
if scope.is_a?(Relation)
-
scope
-
else
-
klass.unscoped.instance_exec(owner, &scope)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Belongs To Association
-
1
module Associations
-
1
class BelongsToAssociation < SingularAssociation #:nodoc:
-
-
1
def handle_dependency
-
target.send(options[:dependent]) if load_target
-
end
-
-
1
def replace(record)
-
44
raise_on_type_mismatch!(record) if record
-
-
44
update_counters(record)
-
44
replace_keys(record)
-
44
set_inverse_instance(record)
-
-
44
@updated = true if record
-
-
44
self.target = record
-
end
-
-
1
def reset
-
190
super
-
190
@updated = false
-
end
-
-
1
def updated?
-
44
@updated
-
end
-
-
1
private
-
-
1
def find_target?
-
117
!loaded? && foreign_key_present? && klass
-
end
-
-
1
def update_counters(record)
-
44
counter_cache_name = reflection.counter_cache_column
-
-
44
if counter_cache_name && owner.persisted? && different_target?(record)
-
if record
-
record.class.increment_counter(counter_cache_name, record.id)
-
end
-
-
if foreign_key_present?
-
klass.decrement_counter(counter_cache_name, target_id)
-
end
-
end
-
end
-
-
# Checks whether record is different to the current target, without loading it
-
1
def different_target?(record)
-
if record.nil?
-
owner[reflection.foreign_key]
-
else
-
record.id != owner[reflection.foreign_key]
-
end
-
end
-
-
1
def replace_keys(record)
-
44
if record
-
44
owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
-
else
-
owner[reflection.foreign_key] = nil
-
end
-
end
-
-
1
def foreign_key_present?
-
73
owner[reflection.foreign_key]
-
end
-
-
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
-
# has_one associations.
-
1
def invertible_for?(record)
-
190
inverse = inverse_reflection_for(record)
-
190
inverse && inverse.macro == :has_one
-
end
-
-
1
def target_id
-
if options[:primary_key]
-
owner.send(reflection.name).try(:id)
-
else
-
owner[reflection.foreign_key]
-
end
-
end
-
-
1
def stale_state
-
392
owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
-
end
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class Association #:nodoc:
-
1
class << self
-
1
attr_accessor :valid_options
-
end
-
-
1
self.valid_options = [:class_name, :foreign_key, :validate]
-
-
1
attr_reader :model, :name, :scope, :options, :reflection
-
-
1
def self.build(*args, &block)
-
14
new(*args, &block).build
-
end
-
-
1
def initialize(model, name, scope, options)
-
14
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
-
-
14
@model = model
-
14
@name = name
-
-
14
if scope.is_a?(Hash)
-
@scope = nil
-
@options = scope
-
else
-
14
@scope = scope
-
14
@options = options
-
end
-
-
14
if @scope && @scope.arity == 0
-
prev_scope = @scope
-
@scope = proc { instance_exec(&prev_scope) }
-
end
-
end
-
-
1
def mixin
-
56
@model.generated_feature_methods
-
end
-
-
2
include Module.new { def build; end }
-
-
1
def build
-
14
validate_options
-
14
define_accessors
-
14
configure_dependency if options[:dependent]
-
14
@reflection = model.create_reflection(macro, name, scope, options, model)
-
14
super # provides an extension point
-
14
@reflection
-
end
-
-
1
def macro
-
raise NotImplementedError
-
end
-
-
1
def valid_options
-
14
Association.valid_options
-
end
-
-
1
def validate_options
-
14
options.assert_valid_keys(valid_options)
-
end
-
-
1
def define_accessors
-
14
define_readers
-
14
define_writers
-
end
-
-
1
def define_readers
-
14
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}(*args)
-
association(:#{name}).reader(*args)
-
end
-
CODE
-
end
-
-
1
def define_writers
-
14
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}=(value)
-
association(:#{name}).writer(value)
-
end
-
CODE
-
end
-
-
1
def configure_dependency
-
6
unless valid_dependent_options.include? options[:dependent]
-
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
-
end
-
-
6
if options[:dependent] == :restrict
-
ActiveSupport::Deprecation.warn(
-
"The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
-
"provides the same functionality."
-
)
-
end
-
-
6
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{macro}_dependent_for_#{name}
-
association(:#{name}).handle_dependency
-
end
-
CODE
-
-
6
model.before_destroy "#{macro}_dependent_for_#{name}"
-
end
-
-
1
def valid_dependent_options
-
raise NotImplementedError
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class BelongsTo < SingularAssociation #:nodoc:
-
1
def macro
-
6
:belongs_to
-
end
-
-
1
def valid_options
-
6
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
-
end
-
-
1
def constructable?
-
6
!options[:polymorphic]
-
end
-
-
1
def build
-
6
reflection = super
-
6
add_counter_cache_callbacks(reflection) if options[:counter_cache]
-
6
add_touch_callbacks(reflection) if options[:touch]
-
6
reflection
-
end
-
-
1
def add_counter_cache_callbacks(reflection)
-
cache_column = reflection.counter_cache_column
-
foreign_key = reflection.foreign_key
-
-
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def belongs_to_counter_cache_after_create_for_#{name}
-
if record = #{name}
-
record.class.increment_counter(:#{cache_column}, record.id)
-
@_after_create_counter_called = true
-
end
-
end
-
-
def belongs_to_counter_cache_before_destroy_for_#{name}
-
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
-
record = #{name}
-
if record && !self.destroyed?
-
record.class.decrement_counter(:#{cache_column}, record.id)
-
end
-
end
-
end
-
-
def belongs_to_counter_cache_after_update_for_#{name}
-
if (@_after_create_counter_called ||= false)
-
@_after_create_counter_called = false
-
elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize})
-
model = #{name.to_s.camelize}
-
foreign_key_was = self.#{foreign_key}_was
-
foreign_key = self.#{foreign_key}
-
-
if foreign_key && model.respond_to?(:increment_counter)
-
model.increment_counter(:#{cache_column}, foreign_key)
-
end
-
if foreign_key_was && model.respond_to?(:decrement_counter)
-
model.decrement_counter(:#{cache_column}, foreign_key_was)
-
end
-
end
-
end
-
CODE
-
-
model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
-
model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
-
model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
-
-
klass = reflection.class_name.safe_constantize
-
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
-
end
-
-
1
def add_touch_callbacks(reflection)
-
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def belongs_to_touch_after_save_or_destroy_for_#{name}
-
foreign_key_field = #{reflection.foreign_key.inspect}
-
old_foreign_id = changed_attributes[foreign_key_field]
-
-
if old_foreign_id
-
association = association(:#{name})
-
reflection = association.reflection
-
if reflection.polymorphic?
-
klass = send("#{reflection.foreign_type}_was").constantize
-
else
-
klass = association.klass
-
end
-
old_record = klass.find_by(klass.primary_key => old_foreign_id)
-
-
if old_record
-
old_record.touch #{options[:touch].inspect if options[:touch] != true}
-
end
-
end
-
-
record = #{name}
-
if record && record.persisted?
-
record.touch #{options[:touch].inspect if options[:touch] != true}
-
end
-
end
-
CODE
-
-
model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
-
model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
-
model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
-
end
-
-
1
def valid_dependent_options
-
[:destroy, :delete]
-
end
-
end
-
end
-
1
require 'active_record/associations'
-
-
1
module ActiveRecord::Associations::Builder
-
1
class CollectionAssociation < Association #:nodoc:
-
-
1
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
-
-
1
def valid_options
-
super + [:table_name, :finder_sql, :counter_sql, :before_add,
-
8
:after_add, :before_remove, :after_remove, :extend]
-
end
-
-
1
attr_reader :block_extension, :extension_module
-
-
1
def initialize(*args, &extension)
-
8
super(*args)
-
8
@block_extension = extension
-
end
-
-
1
def build
-
8
show_deprecation_warnings
-
8
wrap_block_extension
-
8
reflection = super
-
40
CALLBACKS.each { |callback_name| define_callback(callback_name) }
-
8
reflection
-
end
-
-
1
def writable?
-
true
-
end
-
-
1
def show_deprecation_warnings
-
8
[:finder_sql, :counter_sql].each do |name|
-
16
if options.include? name
-
ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).")
-
end
-
end
-
end
-
-
1
def wrap_block_extension
-
8
if block_extension
-
@extension_module = mod = Module.new(&block_extension)
-
silence_warnings do
-
model.parent.const_set(extension_module_name, mod)
-
end
-
-
prev_scope = @scope
-
-
if prev_scope
-
@scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
-
else
-
@scope = proc { extending(mod) }
-
end
-
end
-
end
-
-
1
def extension_module_name
-
@extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
-
end
-
-
1
def define_callback(callback_name)
-
32
full_callback_name = "#{callback_name}_for_#{name}"
-
-
# TODO : why do i need method_defined? I think its because of the inheritance chain
-
32
model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
-
32
model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
-
end
-
-
1
def define_readers
-
8
super
-
-
8
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids
-
association(:#{name}).ids_reader
-
end
-
CODE
-
end
-
-
1
def define_writers
-
8
super
-
-
8
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids=(ids)
-
association(:#{name}).ids_writer(ids)
-
end
-
CODE
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class HasMany < CollectionAssociation #:nodoc:
-
1
def macro
-
20
:has_many
-
end
-
-
1
def valid_options
-
8
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
-
end
-
-
1
def valid_dependent_options
-
6
[:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class SingularAssociation < Association #:nodoc:
-
1
def valid_options
-
6
super + [:remote, :dependent, :primary_key, :inverse_of]
-
end
-
-
1
def constructable?
-
true
-
end
-
-
1
def define_accessors
-
6
super
-
6
define_constructors if constructable?
-
end
-
-
1
def define_constructors
-
6
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def build_#{name}(*args, &block)
-
association(:#{name}).build(*args, &block)
-
end
-
-
def create_#{name}(*args, &block)
-
association(:#{name}).create(*args, &block)
-
end
-
-
def create_#{name}!(*args, &block)
-
association(:#{name}).create!(*args, &block)
-
end
-
CODE
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# = Active Record Association Collection
-
#
-
# CollectionAssociation is an abstract class that provides common stuff to
-
# ease the implementation of association proxies that represent
-
# collections. See the class hierarchy in AssociationProxy.
-
#
-
# CollectionAssociation:
-
# HasAndBelongsToManyAssociation => has_and_belongs_to_many
-
# HasManyAssociation => has_many
-
# HasManyThroughAssociation + ThroughAssociation => has_many :through
-
#
-
# CollectionAssociation class provides common methods to the collections
-
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
-
# +:through association+ option.
-
#
-
# You need to be careful with assumptions regarding the target: The proxy
-
# does not fetch records from the database until it needs them, but new
-
# ones created with +build+ are added to the target. So, the target may be
-
# non-empty and still lack children waiting to be read from the database.
-
# If you look directly to the database you cannot assume that's the entire
-
# collection because new records may have been added to the target, etc.
-
#
-
# If you need to work on all current children, new and existing records,
-
# +load_target+ and the +loaded+ flag are your friends.
-
1
class CollectionAssociation < Association #:nodoc:
-
-
# Implements the reader method, e.g. foo.items for Foo.has_many :items
-
1
def reader(force_reload = false)
-
559
if force_reload
-
klass.uncached { reload }
-
elsif stale_target?
-
reload
-
end
-
-
559
@proxy ||= CollectionProxy.new(klass, self)
-
end
-
-
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
-
1
def writer(records)
-
replace(records)
-
end
-
-
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
-
1
def ids_reader
-
if loaded? || options[:finder_sql]
-
load_target.map do |record|
-
record.send(reflection.association_primary_key)
-
end
-
else
-
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
-
scope.pluck(column)
-
end
-
end
-
-
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
-
1
def ids_writer(ids)
-
pk_column = reflection.primary_key_column
-
ids = Array(ids).reject { |id| id.blank? }
-
ids.map! { |i| pk_column.type_cast(i) }
-
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
-
end
-
-
1
def reset
-
435
super
-
435
@target = []
-
end
-
-
1
def select(select = nil)
-
if block_given?
-
load_target.select.each { |e| yield e }
-
else
-
scope.select(select)
-
end
-
end
-
-
1
def find(*args)
-
if block_given?
-
load_target.find(*args) { |*block_args| yield(*block_args) }
-
else
-
if options[:finder_sql]
-
find_by_scan(*args)
-
elsif options[:inverse_of] && loaded?
-
args_flatten = args.flatten
-
raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
-
-
result = find_by_scan(*args)
-
-
result_size = Array(result).size
-
if !result || result_size != args_flatten.size
-
scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
-
else
-
result
-
end
-
else
-
scope.find(*args)
-
end
-
end
-
end
-
-
1
def first(*args)
-
first_or_last(:first, *args)
-
end
-
-
1
def last(*args)
-
first_or_last(:last, *args)
-
end
-
-
1
def build(attributes = {}, &block)
-
102
if attributes.is_a?(Array)
-
attributes.collect { |attr| build(attr, &block) }
-
else
-
102
add_to_target(build_record(attributes)) do |record|
-
102
yield(record) if block_given?
-
end
-
end
-
end
-
-
1
def create(attributes = {}, &block)
-
create_record(attributes, &block)
-
end
-
-
1
def create!(attributes = {}, &block)
-
39
create_record(attributes, true, &block)
-
end
-
-
# Add +records+ to this association. Returns +self+ so method calls may
-
# be chained. Since << flattens its argument list and inserts each record,
-
# +push+ and +concat+ behave identically.
-
1
def concat(*records)
-
load_target if owner.new_record?
-
-
if owner.new_record?
-
concat_records(records)
-
else
-
transaction { concat_records(records) }
-
end
-
end
-
-
# Starts a transaction in the association class's database connection.
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :books
-
# end
-
#
-
# Author.first.books.transaction do
-
# # same effect as calling Book.transaction
-
# end
-
1
def transaction(*args)
-
40
reflection.klass.transaction(*args) do
-
40
yield
-
end
-
end
-
-
# Remove all records from this association.
-
#
-
# See delete for more info.
-
1
def delete_all
-
delete(:all).tap do
-
reset
-
loaded!
-
end
-
end
-
-
# Destroy all the records from this association.
-
#
-
# See destroy for more info.
-
1
def destroy_all
-
12
destroy(load_target).tap do
-
12
reset
-
12
loaded!
-
end
-
end
-
-
# Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
-
# association, it will be used for the query. Otherwise, construct options and pass them with
-
# scope to the target class's +count+.
-
1
def count(column_name = nil, count_options = {})
-
314
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
-
-
314
if options[:counter_sql] || options[:finder_sql]
-
unless count_options.blank?
-
raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
-
end
-
-
reflection.klass.count_by_sql(custom_counter_sql)
-
else
-
314
relation = scope
-
314
if association_scope.distinct_value
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
-
column_name ||= reflection.klass.primary_key
-
relation = relation.distinct
-
end
-
-
314
value = relation.count(column_name, count_options)
-
-
314
limit = options[:limit]
-
314
offset = options[:offset]
-
-
314
if limit || offset
-
[ [value - offset.to_i, 0].max, limit.to_i ].min
-
else
-
314
value
-
end
-
end
-
end
-
-
# Removes +records+ from this association calling +before_remove+ and
-
# +after_remove+ callbacks.
-
#
-
# This method is abstract in the sense that +delete_records+ has to be
-
# provided by descendants. Note this method does not imply the records
-
# are actually removed from the database, that depends precisely on
-
# +delete_records+. They are in any case removed from the collection.
-
1
def delete(*records)
-
dependent = options[:dependent]
-
-
if records.first == :all
-
-
if dependent && dependent == :destroy
-
message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \
-
'It means if the :dependent option is :destroy then the associated ' \
-
'records would be deleted without loading and invoking callbacks.'
-
-
ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message)
-
end
-
-
if loaded? || dependent == :destroy
-
delete_or_destroy(load_target, dependent)
-
else
-
delete_records(:all, dependent)
-
end
-
else
-
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
-
delete_or_destroy(records, dependent)
-
end
-
end
-
-
# Destroy +records+ and remove them from this association calling
-
# +before_remove+ and +after_remove+ callbacks.
-
#
-
# Note that this method will _always_ remove records from the database
-
# ignoring the +:dependent+ option.
-
1
def destroy(*records)
-
24
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
-
12
delete_or_destroy(records, :destroy)
-
end
-
-
# Returns the size of the collection by executing a SELECT COUNT(*)
-
# query if the collection hasn't been loaded, and calling
-
# <tt>collection.size</tt> if it has.
-
#
-
# If the collection has been already loaded +size+ and +length+ are
-
# equivalent. If not and you are going to need the records anyway
-
# +length+ will take one less query. Otherwise +size+ is more efficient.
-
#
-
# This method is abstract in the sense that it relies on
-
# +count_records+, which is a method descendants have to provide.
-
1
def size
-
if !find_target? || loaded?
-
if association_scope.distinct_value
-
target.uniq.size
-
else
-
target.size
-
end
-
elsif !loaded? && !association_scope.group_values.empty?
-
load_target.size
-
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
-
unsaved_records = target.select { |r| r.new_record? }
-
unsaved_records.size + count_records
-
else
-
count_records
-
end
-
end
-
-
# Returns the size of the collection calling +size+ on the target.
-
#
-
# If the collection has been already loaded +length+ and +size+ are
-
# equivalent. If not and you are going to need the records anyway this
-
# method will take one less query. Otherwise +size+ is more efficient.
-
1
def length
-
load_target.size
-
end
-
-
# Returns true if the collection is empty.
-
#
-
# If the collection has been loaded or the <tt>:counter_sql</tt> option
-
# is provided, it is equivalent to <tt>collection.size.zero?</tt>. If the
-
# collection has not been loaded, it is equivalent to
-
# <tt>collection.exists?</tt>. If the collection has not already been
-
# loaded and you are going to fetch the records anyway it is better to
-
# check <tt>collection.length.zero?</tt>.
-
1
def empty?
-
33
if loaded? || options[:counter_sql]
-
size.zero?
-
else
-
33
@target.blank? && !scope.exists?
-
end
-
end
-
-
# Returns true if the collections is not empty.
-
# Equivalent to +!collection.empty?+.
-
1
def any?
-
34
if block_given?
-
1
load_target.any? { |*block_args| yield(*block_args) }
-
else
-
33
!empty?
-
end
-
end
-
-
# Returns true if the collection has more than 1 record.
-
# Equivalent to +collection.size > 1+.
-
1
def many?
-
if block_given?
-
load_target.many? { |*block_args| yield(*block_args) }
-
else
-
size > 1
-
end
-
end
-
-
1
def distinct
-
seen = {}
-
load_target.find_all do |record|
-
seen[record.id] = true unless seen.key?(record.id)
-
end
-
end
-
1
alias uniq distinct
-
-
# Replace this collection with +other_array+. This will perform a diff
-
# and delete/add only records that have changed.
-
1
def replace(other_array)
-
other_array.each { |val| raise_on_type_mismatch!(val) }
-
original_target = load_target.dup
-
-
if owner.new_record?
-
replace_records(other_array, original_target)
-
else
-
transaction { replace_records(other_array, original_target) }
-
end
-
end
-
-
1
def include?(record)
-
3
if record.is_a?(reflection.klass)
-
3
if record.new_record?
-
include_in_memory?(record)
-
else
-
3
load_target if options[:finder_sql]
-
3
loaded? ? target.include?(record) : scope.exists?(record)
-
end
-
else
-
false
-
end
-
end
-
-
1
def load_target
-
28
if find_target?
-
15
@target = merge_target_lists(find_target, target)
-
end
-
-
28
loaded!
-
28
target
-
end
-
-
1
def add_to_target(record)
-
141
callback(:before_add, record)
-
141
yield(record) if block_given?
-
-
141
if association_scope.distinct_value && index = @target.index(record)
-
@target[index] = record
-
else
-
141
@target << record
-
end
-
-
141
callback(:after_add, record)
-
141
set_inverse_instance(record)
-
-
141
record
-
end
-
-
1
def scope(opts = {})
-
1026
scope = super()
-
1026
scope.none! if opts.fetch(:nullify, true) && null_scope?
-
1026
scope
-
end
-
-
1
def null_scope?
-
614
owner.new_record? && !foreign_key_present?
-
end
-
-
1
private
-
-
1
def custom_counter_sql
-
if options[:counter_sql]
-
interpolate(options[:counter_sql])
-
else
-
# replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
-
interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
-
count_with = $2.to_s
-
count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
-
"SELECT #{$1}COUNT(#{count_with}) FROM"
-
end
-
end
-
end
-
-
1
def custom_finder_sql
-
interpolate(options[:finder_sql])
-
end
-
-
1
def find_target
-
14
records =
-
if options[:finder_sql]
-
reflection.klass.find_by_sql(custom_finder_sql)
-
else
-
14
scope.to_a
-
end
-
-
21
records.each { |record| set_inverse_instance(record) }
-
14
records
-
end
-
-
# We have some records loaded from the database (persisted) and some that are
-
# in-memory (memory). The same record may be represented in the persisted array
-
# and in the memory array.
-
#
-
# So the task of this method is to merge them according to the following rules:
-
#
-
# * The final array must not have duplicates
-
# * The order of the persisted array is to be preserved
-
# * Any changes made to attributes on objects in the memory array are to be preserved
-
# * Otherwise, attributes should have the value found in the database
-
1
def merge_target_lists(persisted, memory)
-
15
return persisted if memory.empty?
-
1
return memory if persisted.empty?
-
-
1
persisted.map! do |record|
-
3
if mem_record = memory.delete(record)
-
-
3
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
-
18
mem_record[name] = record[name]
-
end
-
-
3
mem_record
-
else
-
record
-
end
-
end
-
-
1
persisted + memory
-
end
-
-
1
def create_record(attributes, raise = false, &block)
-
39
unless owner.persisted?
-
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
-
end
-
-
39
if attributes.is_a?(Array)
-
attributes.collect { |attr| create_record(attr, raise, &block) }
-
else
-
39
transaction do
-
39
add_to_target(build_record(attributes)) do |record|
-
39
yield(record) if block_given?
-
39
insert_record(record, true, raise)
-
end
-
end
-
end
-
end
-
-
# Do the relevant stuff to insert the given record into the association collection.
-
1
def insert_record(record, validate = true, raise = false)
-
raise NotImplementedError
-
end
-
-
1
def create_scope
-
141
scope.scope_for_create.stringify_keys
-
end
-
-
1
def delete_or_destroy(records, method)
-
12
records = records.flatten
-
14
records.each { |record| raise_on_type_mismatch!(record) }
-
14
existing_records = records.reject { |r| r.new_record? }
-
-
12
if existing_records.empty?
-
11
remove_records(existing_records, records, method)
-
else
-
2
transaction { remove_records(existing_records, records, method) }
-
end
-
end
-
-
1
def remove_records(existing_records, records, method)
-
14
records.each { |record| callback(:before_remove, record) }
-
-
12
delete_records(existing_records, method) if existing_records.any?
-
14
records.each { |record| target.delete(record) }
-
-
14
records.each { |record| callback(:after_remove, record) }
-
end
-
-
# Delete the given records from the association, using one of the methods :destroy,
-
# :delete_all or :nullify (or nil, in which case a default is used).
-
1
def delete_records(records, method)
-
raise NotImplementedError
-
end
-
-
1
def replace_records(new_target, original_target)
-
delete(target - new_target)
-
-
unless concat(new_target - target)
-
@target = original_target
-
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
-
"new records could not be saved."
-
end
-
-
target
-
end
-
-
1
def concat_records(records)
-
result = true
-
-
records.flatten.each do |record|
-
raise_on_type_mismatch!(record)
-
add_to_target(record) do |rec|
-
result &&= insert_record(rec) unless owner.new_record?
-
end
-
end
-
-
result && records
-
end
-
-
1
def callback(method, record)
-
286
callbacks_for(method).each do |callback|
-
case callback
-
when Symbol
-
owner.send(callback, record)
-
when Proc
-
callback.call(owner, record)
-
else
-
callback.send(method, owner, record)
-
end
-
end
-
end
-
-
1
def callbacks_for(callback_name)
-
286
full_callback_name = "#{callback_name}_for_#{reflection.name}"
-
286
owner.class.send(full_callback_name.to_sym) || []
-
end
-
-
# Should we deal with assoc.first or assoc.last by issuing an independent query to
-
# the database, or by getting the target, and then taking the first/last item from that?
-
#
-
# If the args is just a non-empty options hash, go to the database.
-
#
-
# Otherwise, go to the database only if none of the following are true:
-
# * target already loaded
-
# * owner is new record
-
# * custom :finder_sql exists
-
# * target contains new or changed record(s)
-
# * the first arg is an integer (which indicates the number of records to be returned)
-
1
def fetch_first_or_last_using_find?(args)
-
if args.first.is_a?(Hash)
-
true
-
else
-
!(loaded? ||
-
owner.new_record? ||
-
options[:finder_sql] ||
-
target.any? { |record| record.new_record? || record.changed? } ||
-
args.first.kind_of?(Integer))
-
end
-
end
-
-
1
def include_in_memory?(record)
-
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
-
owner.send(reflection.through_reflection.name).any? { |source|
-
target = source.send(reflection.source_reflection.name)
-
target.respond_to?(:include?) ? target.include?(record) : target == record
-
} || target.include?(record)
-
else
-
target.include?(record)
-
end
-
end
-
-
# If using a custom finder_sql or if the :inverse_of option has been
-
# specified, then #find scans the entire collection.
-
1
def find_by_scan(*args)
-
expects_array = args.first.kind_of?(Array)
-
ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
-
-
if ids.size == 1
-
id = ids.first
-
record = load_target.detect { |r| id == r.id.to_s }
-
expects_array ? [ record ] : record
-
else
-
load_target.select { |r| ids.include?(r.id.to_s) }
-
end
-
end
-
-
# Fetches the first/last using SQL if possible, otherwise from the target array.
-
1
def first_or_last(type, *args)
-
args.shift if args.first.is_a?(Hash) && args.first.empty?
-
-
collection = fetch_first_or_last_using_find?(args) ? scope : load_target
-
collection.send(type, *args).tap do |record|
-
set_inverse_instance record if record.is_a? ActiveRecord::Base
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# Association proxies in Active Record are middlemen between the object that
-
# holds the association, known as the <tt>@owner</tt>, and the actual associated
-
# object, known as the <tt>@target</tt>. The kind of association any proxy is
-
# about is available in <tt>@reflection</tt>. That's an instance of the class
-
# ActiveRecord::Reflection::AssociationReflection.
-
#
-
# For example, given
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :posts
-
# end
-
#
-
# blog = Blog.first
-
#
-
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
-
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
-
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
-
#
-
# This class delegates unknown methods to <tt>@target</tt> via
-
# <tt>method_missing</tt>.
-
#
-
# The <tt>@target</tt> object is not \loaded until needed. For example,
-
#
-
# blog.posts.count
-
#
-
# is computed directly through SQL and does not trigger by itself the
-
# instantiation of the actual post records.
-
1
class CollectionProxy < Relation
-
1
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
-
-
1
def initialize(klass, association) #:nodoc:
-
412
@association = association
-
412
super klass, klass.arel_table
-
412
self.default_scoped = true
-
412
merge! association.scope(nullify: false)
-
end
-
-
1
def target
-
@association.target
-
end
-
-
1
def load_target
-
3
@association.load_target
-
end
-
-
# Returns +true+ if the association has been loaded, otherwise +false+.
-
#
-
# person.pets.loaded? # => false
-
# person.pets
-
# person.pets.loaded? # => true
-
1
def loaded?
-
@association.loaded?
-
end
-
-
# Works in two ways.
-
#
-
# *First:* Specify a subset of fields to be selected from the result set.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet id: nil, name: "Fancy-Fancy">,
-
# # #<Pet id: nil, name: "Spook">,
-
# # #<Pet id: nil, name: "Choo-Choo">
-
# # ]
-
#
-
# person.pets.select([:id, :name])
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy">,
-
# # #<Pet id: 2, name: "Spook">,
-
# # #<Pet id: 3, name: "Choo-Choo">
-
# # ]
-
#
-
# Be careful because this also means you're initializing a model
-
# object with only the fields that you've selected. If you attempt
-
# to access a field that is not in the initialized record you'll
-
# receive:
-
#
-
# person.pets.select(:name).first.person_id
-
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
-
#
-
# *Second:* You can pass a block so it can be used just like Array#select.
-
# This builds an array of objects from the database for the scope,
-
# converting them into an array and iterating through them using
-
# Array#select.
-
#
-
# person.pets.select { |pet| pet.name =~ /oo/ }
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.select(:name) { |pet| pet.name =~ /oo/ }
-
# # => [
-
# # #<Pet id: 2, name: "Spook">,
-
# # #<Pet id: 3, name: "Choo-Choo">
-
# # ]
-
1
def select(select = nil, &block)
-
@association.select(select, &block)
-
end
-
-
# Finds an object in the collection responding to the +id+. Uses the same
-
# rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
-
# error if the object can not be found.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
# person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
-
#
-
# person.pets.find(2) { |pet| pet.name.downcase! }
-
# # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
-
#
-
# person.pets.find(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def find(*args, &block)
-
@association.find(*args, &block)
-
end
-
-
# Returns the first record, or the first +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.first(2)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.first # => nil
-
# another_person_without.pets.first(3) # => []
-
1
def first(*args)
-
@association.first(*args)
-
end
-
-
# Returns the last record, or the last +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
#
-
# person.pets.last(2)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.last # => nil
-
# another_person_without.pets.last(3) # => []
-
1
def last(*args)
-
@association.last(*args)
-
end
-
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object, but have not yet been saved.
-
# You can pass an array of attributes hashes, this will return an array
-
# with the new objects.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.build
-
# # => #<Pet id: nil, name: nil, person_id: 1>
-
#
-
# person.pets.build(name: 'Fancy-Fancy')
-
# # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
-
# # => [
-
# # #<Pet id: nil, name: "Spook", person_id: 1>,
-
# # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
-
# # #<Pet id: nil, name: "Brain", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 5 # size of the collection
-
# person.pets.count # => 0 # count from database
-
1
def build(attributes = {}, &block)
-
102
@association.build(attributes, &block)
-
end
-
1
alias_method :new, :build
-
-
# Returns a new object of the collection type that has been instantiated with
-
# attributes, linked to this object and that has already been saved (if it
-
# passes the validations).
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.create(name: 'Fancy-Fancy')
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# person.pets.count # => 3
-
#
-
# person.pets.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def create(attributes = {}, &block)
-
@association.create(attributes, &block)
-
end
-
-
# Like +create+, except that if the record is invalid, raises an exception.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# class Pet
-
# validates :name, presence: true
-
# end
-
#
-
# person.pets.create!(name: nil)
-
# # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
-
1
def create!(attributes = {}, &block)
-
39
@association.create!(attributes, &block)
-
end
-
-
# Add one or more records to the collection by setting their foreign keys
-
# to the association's primary key. Since << flattens its argument list and
-
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
-
# so method calls may be chained.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 0
-
# person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
-
# person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
-
# person.pets.size # => 3
-
#
-
# person.id # => 1
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
-
# person.pets.size # => 5
-
1
def concat(*records)
-
@association.concat(*records)
-
end
-
-
# Replaces this collection with +other_array+. This will perform a diff
-
# and delete/add only records that have changed.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
-
#
-
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
-
#
-
# person.pets.replace(other_pets)
-
#
-
# person.pets
-
# # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
-
#
-
# If the supplied array has an incorrect association type, it raises
-
# an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
-
#
-
# person.pets.replace(["doo", "ggie", "gaga"])
-
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
-
1
def replace(other_array)
-
@association.replace(other_array)
-
end
-
-
# Deletes all the records from the collection. For +has_many+ associations,
-
# the deletion is done according to the strategy specified by the <tt>:dependent</tt>
-
# option. Returns an array with the deleted records.
-
#
-
# If no <tt>:dependent</tt> option is given, then it will follow the
-
# default strategy. The default strategy is <tt>:nullify</tt>. This
-
# sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
-
# the default strategy is +delete_all+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
-
# # #<Pet id: 2, name: "Spook", person_id: nil>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
-
# # ]
-
#
-
# If it is set to <tt>:destroy</tt> all the objects from the collection
-
# are removed by calling their +destroy+ method. See +destroy+ for more
-
# information.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound
-
#
-
# If it is set to <tt>:delete_all</tt>, all the objects are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound
-
1
def delete_all
-
@association.delete_all
-
end
-
-
# Deletes the records of the collection directly from the database.
-
# This will _always_ remove the records ignoring the +:dependent+
-
# option.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy_all
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1) # => Couldn't find Pet with id=1
-
1
def destroy_all
-
@association.destroy_all
-
end
-
-
# Deletes the +records+ supplied and removes them from the collection. For
-
# +has_many+ associations, the deletion is done according to the strategy
-
# specified by the <tt>:dependent</tt> option. Returns an array with the
-
# deleted records.
-
#
-
# If no <tt>:dependent</tt> option is given, then it will follow the default
-
# strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
-
# keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
-
# strategy is +delete_all+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
-
#
-
# If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
-
# their +destroy+ method. See +destroy+ for more information.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1), Pet.find(3))
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 1
-
# person.pets
-
# # => [#<Pet id: 2, name: "Spook", person_id: 1>]
-
#
-
# Pet.find(1, 3)
-
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
-
#
-
# If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
-
#
-
# You can pass +Fixnum+ or +String+ values, it finds the records
-
# responding to the +id+ and executes delete on them.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete("1")
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.delete(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def delete(*records)
-
@association.delete(*records)
-
end
-
-
# Destroys the +records+ supplied and removes them from the collection.
-
# This method will _always_ remove record from the database ignoring
-
# the +:dependent+ option. Returns an array with the removed records.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(2), Pet.find(3))
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
-
#
-
# You can pass +Fixnum+ or +String+ values, it finds the records
-
# responding to the +id+ and then deletes them from the database.
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy("4")
-
# # => #<Pet id: 4, name: "Benny", person_id: 1>
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(5, 6)
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
-
1
def destroy(*records)
-
@association.destroy(*records)
-
end
-
-
# Specifies whether the records should be unique or not.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet name: "Fancy-Fancy">,
-
# # #<Pet name: "Fancy-Fancy">
-
# # ]
-
#
-
# person.pets.select(:name).distinct
-
# # => [#<Pet name: "Fancy-Fancy">]
-
1
def distinct
-
@association.distinct
-
end
-
1
alias uniq distinct
-
-
# Count all records using SQL.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def count(column_name = nil, options = {})
-
314
@association.count(column_name, options)
-
end
-
-
# Returns the size of the collection. If the collection hasn't been loaded,
-
# it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
-
#
-
# If the collection has been already loaded +size+ and +length+ are
-
# equivalent. If not and you are going to need the records anyway
-
# +length+ will take one less query. Otherwise +size+ is more efficient.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# person.pets # This will execute a SELECT * FROM query
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# # Because the collection is already loaded, this will behave like
-
# # collection.size and no SQL count query is executed.
-
1
def size
-
@association.size
-
end
-
-
# Returns the size of the collection calling +size+ on the target.
-
# If the collection has been already loaded, +length+ and +size+ are
-
# equivalent. If not and you are going to need the records anyway this
-
# method will take one less query. Otherwise +size+ is more efficient.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.length # => 3
-
# # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# # Because the collection is loaded, you can
-
# # call the collection with no additional queries:
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def length
-
@association.length
-
end
-
-
# Returns +true+ if the collection is empty. If the collection has been
-
# loaded or the <tt>:counter_sql</tt> option is provided, it is equivalent
-
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
-
# it is equivalent to <tt>collection.exists?</tt>. If the collection has
-
# not already been loaded and you are going to fetch the records anyway it
-
# is better to check <tt>collection.length.zero?</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 1
-
# person.pets.empty? # => false
-
#
-
# person.pets.delete_all
-
#
-
# person.pets.count # => 0
-
# person.pets.empty? # => true
-
1
def empty?
-
@association.empty?
-
end
-
-
# Returns +true+ if the collection is not empty.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 0
-
# person.pets.any? # => false
-
#
-
# person.pets << Pet.new(name: 'Snoop')
-
# person.pets.count # => 0
-
# person.pets.any? # => true
-
#
-
# You can also pass a block to define criteria. The behavior
-
# is the same, it returns true if the collection based on the
-
# criteria is not empty.
-
#
-
# person.pets
-
# # => [#<Pet name: "Snoop", group: "dogs">]
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => false
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => true
-
1
def any?(&block)
-
34
@association.any?(&block)
-
end
-
-
# Returns true if the collection has more than one record.
-
# Equivalent to <tt>collection.size > 1</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count #=> 1
-
# person.pets.many? #=> false
-
#
-
# person.pets << Pet.new(name: 'Snoopy')
-
# person.pets.count #=> 2
-
# person.pets.many? #=> true
-
#
-
# You can also pass a block to define criteria. The
-
# behavior is the same, it returns true if the collection
-
# based on the criteria has more than one record.
-
#
-
# person.pets
-
# # => [
-
# # #<Pet name: "Gorby", group: "cats">,
-
# # #<Pet name: "Puff", group: "cats">,
-
# # #<Pet name: "Snoop", group: "dogs">
-
# # ]
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => false
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => true
-
1
def many?(&block)
-
@association.many?(&block)
-
end
-
-
# Returns +true+ if the given object is present in the collection.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # => [#<Pet id: 20, name: "Snoop">]
-
#
-
# person.pets.include?(Pet.find(20)) # => true
-
# person.pets.include?(Pet.find(21)) # => false
-
1
def include?(record)
-
3
!!@association.include?(record)
-
end
-
-
1
def proxy_association
-
@association
-
end
-
-
# We don't want this object to be put on the scoping stack, because
-
# that could create an infinite loop where we call an @association
-
# method, which gets the current scope, which is this object, which
-
# delegates to @association, and so on.
-
1
def scoping
-
78
@association.scope.scoping { yield }
-
end
-
-
# Returns a <tt>Relation</tt> object for the records in this association
-
1
def scope
-
69
@association.scope
-
end
-
-
# :nodoc:
-
1
alias spawn scope
-
-
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
-
# contain the same number of elements and if each element is equal
-
# to the corresponding element in the other array, otherwise returns
-
# +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# other = person.pets.to_ary
-
#
-
# person.pets == other
-
# # => true
-
#
-
# other = [Pet.new(id: 1), Pet.new(id: 2)]
-
#
-
# person.pets == other
-
# # => false
-
1
def ==(other)
-
load_target == other
-
end
-
-
# Returns a new array of objects from the collection. If the collection
-
# hasn't been loaded, it fetches the records from the database.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets = person.pets.to_ary
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets.replace([Pet.new(name: 'BooGoo')])
-
#
-
# other_pets
-
# # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
-
#
-
# person.pets
-
# # This is not affected by replace
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
1
def to_ary
-
3
load_target.dup
-
end
-
1
alias_method :to_a, :to_ary
-
-
# Adds one or more +records+ to the collection by setting their foreign keys
-
# to the association's primary key. Returns +self+, so several appends may be
-
# chained together.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 0
-
# person.pets << Pet.new(name: 'Fancy-Fancy')
-
# person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
-
# person.pets.size # => 3
-
#
-
# person.id # => 1
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def <<(*records)
-
proxy_association.concat(records) && self
-
end
-
1
alias_method :push, :<<
-
1
alias_method :append, :<<
-
-
1
def prepend(*args)
-
raise NoMethodError, "prepend on association is not defined. Please use << or append"
-
end
-
-
# Equivalent to +delete_all+. The difference is that returns +self+, instead
-
# of an array with the deleted objects, so methods can be chained. See
-
# +delete_all+ for more information.
-
1
def clear
-
delete_all
-
self
-
end
-
-
# Reloads the collection from the database. Returns +self+.
-
# Equivalent to <tt>collection(true)</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets # uses the pets cache
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets.reload # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets(true) # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
1
def reload
-
proxy_association.reload
-
self
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Has Many Association
-
1
module Associations
-
# This is the proxy that handles a has many association.
-
#
-
# If the association has a <tt>:through</tt> option further specialization
-
# is provided by its child HasManyThroughAssociation.
-
1
class HasManyAssociation < CollectionAssociation #:nodoc:
-
-
1
def handle_dependency
-
12
case options[:dependent]
-
when :restrict, :restrict_with_exception
-
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
-
-
when :restrict_with_error
-
unless empty?
-
record = klass.human_attribute_name(reflection.name).downcase
-
owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
-
false
-
end
-
-
else
-
12
if options[:dependent] == :destroy
-
# No point in executing the counter update since we're going to destroy the parent anyway
-
14
load_target.each { |t| t.destroyed_by_association = reflection }
-
12
destroy_all
-
else
-
delete_all
-
end
-
end
-
end
-
-
1
def insert_record(record, validate = true, raise = false)
-
39
set_owner_attributes(record)
-
39
set_inverse_instance(record)
-
-
39
if raise
-
39
record.save!(:validate => validate)
-
else
-
record.save(:validate => validate)
-
end
-
end
-
-
1
private
-
-
# Returns the number of records in this collection.
-
#
-
# If the association has a counter cache it gets that value. Otherwise
-
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
-
# there's one. Some configuration options like :group make it impossible
-
# to do an SQL count, in those cases the array count will be used.
-
#
-
# That does not depend on whether the collection has already been loaded
-
# or not. The +size+ method is the one that takes the loaded flag into
-
# account and delegates to +count_records+ if needed.
-
#
-
# If the collection is empty the target is set to an empty array and
-
# the loaded flag is set to true as well.
-
1
def count_records
-
count = if has_cached_counter?
-
owner.send(:read_attribute, cached_counter_attribute_name)
-
elsif options[:counter_sql] || options[:finder_sql]
-
reflection.klass.count_by_sql(custom_counter_sql)
-
else
-
scope.count
-
end
-
-
# If there's nothing in the database and @target has no new records
-
# we are certain the current target is an empty array. This is a
-
# documented side-effect of the method that may avoid an extra SELECT.
-
@target ||= [] and loaded! if count == 0
-
-
[association_scope.limit_value, count].compact.min
-
end
-
-
1
def has_cached_counter?(reflection = reflection())
-
1
owner.attribute_present?(cached_counter_attribute_name(reflection))
-
end
-
-
1
def cached_counter_attribute_name(reflection = reflection())
-
2
options[:counter_cache] || "#{reflection.name}_count"
-
end
-
-
1
def update_counter(difference, reflection = reflection())
-
1
if has_cached_counter?(reflection)
-
counter = cached_counter_attribute_name(reflection)
-
owner.class.update_counters(owner.id, counter => difference)
-
owner[counter] += difference
-
owner.changed_attributes.delete(counter) # eww
-
end
-
end
-
-
# This shit is nasty. We need to avoid the following situation:
-
#
-
# * An associated record is deleted via record.destroy
-
# * Hence the callbacks run, and they find a belongs_to on the record with a
-
# :counter_cache options which points back at our owner. So they update the
-
# counter cache.
-
# * In which case, we must make sure to *not* update the counter cache, or else
-
# it will be decremented twice.
-
#
-
# Hence this method.
-
1
def inverse_updates_counter_cache?(reflection = reflection())
-
1
counter_name = cached_counter_attribute_name(reflection)
-
1
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
-
2
inverse_reflection.counter_cache_column == counter_name
-
}
-
end
-
-
# Deletes the records according to the <tt>:dependent</tt> option.
-
1
def delete_records(records, method)
-
1
if method == :destroy
-
3
records.each { |r| r.destroy }
-
1
update_counter(-records.length) unless inverse_updates_counter_cache?
-
else
-
if records == :all
-
scope = self.scope
-
else
-
scope = self.scope.where(reflection.klass.primary_key => records)
-
end
-
-
if method == :delete_all
-
update_counter(-scope.delete_all)
-
else
-
update_counter(-scope.update_all(reflection.foreign_key => nil))
-
end
-
end
-
end
-
-
1
def foreign_key_present?
-
owner.attribute_present?(reflection.association_primary_key)
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
# = Active Record Has Many Through Association
-
1
module Associations
-
1
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
-
1
include ThroughAssociation
-
-
1
def initialize(owner, reflection)
-
225
super
-
-
225
@through_records = {}
-
225
@through_association = nil
-
end
-
-
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
-
# loaded and calling collection.size if it has. If it's more likely than not that the collection does
-
# have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
-
# SELECT query if you use #length.
-
1
def size
-
if has_cached_counter?
-
owner.send(:read_attribute, cached_counter_attribute_name)
-
elsif loaded?
-
target.size
-
else
-
count
-
end
-
end
-
-
1
def concat(*records)
-
unless owner.new_record?
-
records.flatten.each do |record|
-
raise_on_type_mismatch!(record)
-
record.save! if record.new_record?
-
end
-
end
-
-
super
-
end
-
-
1
def concat_records(records)
-
ensure_not_nested
-
-
records = super
-
-
if owner.new_record? && records
-
records.flatten.each do |record|
-
build_through_record(record)
-
end
-
end
-
-
records
-
end
-
-
1
def insert_record(record, validate = true, raise = false)
-
ensure_not_nested
-
-
if record.new_record?
-
if raise
-
record.save!(:validate => validate)
-
else
-
return unless record.save(:validate => validate)
-
end
-
end
-
-
save_through_record(record)
-
update_counter(1)
-
record
-
end
-
-
1
private
-
-
1
def through_association
-
@through_association ||= owner.association(through_reflection.name)
-
end
-
-
# We temporarily cache through record that has been build, because if we build a
-
# through record in build_record and then subsequently call insert_record, then we
-
# want to use the exact same object.
-
#
-
# However, after insert_record has been called, we clear the cache entry because
-
# we want it to be possible to have multiple instances of the same record in an
-
# association
-
1
def build_through_record(record)
-
@through_records[record.object_id] ||= begin
-
ensure_mutable
-
-
through_record = through_association.build
-
through_record.send("#{source_reflection.name}=", record)
-
through_record
-
end
-
end
-
-
1
def save_through_record(record)
-
build_through_record(record).save!
-
ensure
-
@through_records.delete(record.object_id)
-
end
-
-
1
def build_record(attributes)
-
ensure_not_nested
-
-
record = super(attributes)
-
-
inverse = source_reflection.inverse_of
-
if inverse
-
if inverse.macro == :has_many
-
record.send(inverse.name) << build_through_record(record)
-
elsif inverse.macro == :has_one
-
record.send("#{inverse.name}=", build_through_record(record))
-
end
-
end
-
-
record
-
end
-
-
1
def target_reflection_has_associated_record?
-
1
!(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
-
end
-
-
1
def update_through_counter?(method)
-
case method
-
when :destroy
-
!inverse_updates_counter_cache?(through_reflection)
-
when :nullify
-
false
-
else
-
true
-
end
-
end
-
-
1
def delete_records(records, method)
-
ensure_not_nested
-
-
# This is unoptimised; it will load all the target records
-
# even when we just want to delete everything.
-
records = load_target if records == :all
-
-
scope = through_association.scope
-
scope.where! construct_join_attributes(*records)
-
-
case method
-
when :destroy
-
count = scope.destroy_all.length
-
when :nullify
-
count = scope.update_all(source_reflection.foreign_key => nil)
-
else
-
count = scope.delete_all
-
end
-
-
delete_through_records(records)
-
-
if source_reflection.options[:counter_cache] && method != :destroy
-
counter = source_reflection.counter_cache_column
-
klass.decrement_counter counter, records.map(&:id)
-
end
-
-
if through_reflection.macro == :has_many && update_through_counter?(method)
-
update_counter(-count, through_reflection)
-
end
-
-
update_counter(-count)
-
end
-
-
1
def through_records_for(record)
-
attributes = construct_join_attributes(record)
-
candidates = Array.wrap(through_association.target)
-
candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
-
end
-
-
1
def delete_through_records(records)
-
records.each do |record|
-
through_records = through_records_for(record)
-
-
if through_reflection.macro == :has_many
-
through_records.each { |r| through_association.target.delete(r) }
-
else
-
if through_records.include?(through_association.target)
-
through_association.target = nil
-
end
-
end
-
-
@through_records.delete(record.object_id)
-
end
-
end
-
-
1
def find_target
-
1
return [] unless target_reflection_has_associated_record?
-
1
scope.to_a
-
end
-
-
# NOTE - not sure that we can actually cope with inverses here
-
1
def invertible_for?(record)
-
6
false
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
1
autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
-
1
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
-
1
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
-
-
1
attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
-
-
# base is the base class on which operation is taking place.
-
# associations is the list of associations which are joined using hash, symbol or array.
-
# joins is the list of all string join commnads and arel nodes.
-
#
-
# Example :
-
#
-
# class Physician < ActiveRecord::Base
-
# has_many :appointments
-
# has_many :patients, through: :appointments
-
# end
-
#
-
# If I execute `@physician.patients.to_a` then
-
# base #=> Physician
-
# associations #=> []
-
# joins #=> [#<Arel::Nodes::InnerJoin: ...]
-
#
-
# However if I execute `Physician.joins(:appointments).to_a` then
-
# base #=> Physician
-
# associations #=> [:appointments]
-
# joins #=> []
-
#
-
1
def initialize(base, associations, joins)
-
1028
@base_klass = base
-
1028
@table_joins = joins
-
1028
@join_parts = [JoinBase.new(base)]
-
1028
@associations = {}
-
1028
@reflections = []
-
1028
@alias_tracker = AliasTracker.new(base.connection, joins)
-
1028
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
-
1028
build(associations)
-
end
-
-
1
def graft(*associations)
-
474
associations.each do |association|
-
join_associations.detect {|a| association == a} ||
-
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
-
end
-
474
self
-
end
-
-
1
def join_associations
-
1028
join_parts.last(join_parts.length - 1)
-
end
-
-
1
def join_base
-
join_parts.first
-
end
-
-
1
def columns
-
join_parts.collect { |join_part|
-
554
table = join_part.aliased_table
-
554
join_part.column_names_with_alias.collect{ |column_name, aliased_name|
-
4887
table[column_name].as Arel.sql(aliased_name)
-
}
-
554
}.flatten
-
end
-
-
1
def instantiate(rows)
-
primary_key = join_base.aliased_primary_key
-
parents = {}
-
-
records = rows.map { |model|
-
primary_id = model[primary_key]
-
parent = parents[primary_id] ||= join_base.instantiate(model)
-
construct(parent, @associations, join_associations, model)
-
parent
-
}.uniq
-
-
remove_duplicate_results!(base_klass, records, @associations)
-
records
-
end
-
-
1
protected
-
-
1
def remove_duplicate_results!(base, records, associations)
-
case associations
-
when Symbol, String
-
reflection = base.reflections[associations]
-
remove_uniq_by_reflection(reflection, records)
-
when Array
-
associations.each do |association|
-
remove_duplicate_results!(base, records, association)
-
end
-
when Hash
-
associations.each_key do |name|
-
reflection = base.reflections[name]
-
remove_uniq_by_reflection(reflection, records)
-
-
parent_records = []
-
records.each do |record|
-
if descendant = record.send(reflection.name)
-
if reflection.collection?
-
parent_records.concat descendant.target.uniq
-
else
-
parent_records << descendant
-
end
-
end
-
end
-
-
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
-
end
-
end
-
end
-
-
1
def cache_joined_association(association)
-
associations = []
-
parent = association.parent
-
while parent != join_base
-
associations.unshift(parent.reflection.name)
-
parent = parent.parent
-
end
-
ref = @associations
-
associations.each do |key|
-
ref = ref[key]
-
end
-
ref[association.reflection.name] ||= {}
-
end
-
-
1
def build(associations, parent = nil, join_type = Arel::InnerJoin)
-
1028
parent ||= join_parts.last
-
1028
case associations
-
when Symbol, String
-
reflection = parent.reflections[associations.intern] or
-
raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
-
unless join_association = find_join_association(reflection, parent)
-
@reflections << reflection
-
join_association = build_join_association(reflection, parent)
-
join_association.join_type = join_type
-
@join_parts << join_association
-
cache_joined_association(join_association)
-
end
-
join_association
-
when Array
-
1028
associations.each do |association|
-
build(association, parent, join_type)
-
end
-
when Hash
-
associations.keys.sort_by { |a| a.to_s }.each do |name|
-
join_association = build(name, parent, join_type)
-
build(associations[name], join_association, join_type)
-
end
-
else
-
raise ConfigurationError, associations.inspect
-
end
-
end
-
-
1
def find_join_association(name_or_reflection, parent)
-
if String === name_or_reflection
-
name_or_reflection = name_or_reflection.to_sym
-
end
-
-
join_associations.detect { |j|
-
j.reflection == name_or_reflection && j.parent == parent
-
}
-
end
-
-
1
def remove_uniq_by_reflection(reflection, records)
-
if reflection && reflection.collection?
-
records.each { |record| record.send(reflection.name).target.uniq! }
-
end
-
end
-
-
1
def build_join_association(reflection, parent)
-
JoinAssociation.new(reflection, self, parent)
-
end
-
-
1
def construct(parent, associations, join_parts, row)
-
case associations
-
when Symbol, String
-
name = associations.to_s
-
-
join_part = join_parts.detect { |j|
-
j.reflection.name.to_s == name &&
-
j.parent_table_name == parent.class.table_name }
-
-
raise(ConfigurationError, "No such association") unless join_part
-
-
join_parts.delete(join_part)
-
construct_association(parent, join_part, row)
-
when Array
-
associations.each do |association|
-
construct(parent, association, join_parts, row)
-
end
-
when Hash
-
associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
-
association = construct(parent, association_name, join_parts, row)
-
construct(association, assoc, join_parts, row) if association
-
end
-
else
-
raise ConfigurationError, associations.inspect
-
end
-
end
-
-
1
def construct_association(record, join_part, row)
-
return if record.id.to_s != join_part.parent.record_id(row).to_s
-
-
macro = join_part.reflection.macro
-
if macro == :has_one
-
return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
-
set_target_and_inverse(join_part, association, record)
-
else
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
-
case macro
-
when :has_many, :has_and_belongs_to_many
-
other = record.association(join_part.reflection.name)
-
other.loaded!
-
other.target.push(association) if association
-
other.set_inverse_instance(association)
-
when :belongs_to
-
set_target_and_inverse(join_part, association, record)
-
else
-
raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
-
end
-
end
-
association
-
end
-
-
1
def set_target_and_inverse(join_part, association, record)
-
other = record.association(join_part.reflection.name)
-
other.target = association
-
other.set_inverse_instance(association)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
1
class JoinAssociation < JoinPart # :nodoc:
-
1
include JoinHelper
-
-
# The reflection of the association represented
-
1
attr_reader :reflection
-
-
# The JoinDependency object which this JoinAssociation exists within. This is mainly
-
# relevant for generating aliases which do not conflict with other joins which are
-
# part of the query.
-
1
attr_reader :join_dependency
-
-
# A JoinBase instance representing the active record we are joining onto.
-
# (So in Author.has_many :posts, the Author would be that base record.)
-
1
attr_reader :parent
-
-
# What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
-
1
attr_accessor :join_type
-
-
# These implement abstract methods from the superclass
-
1
attr_reader :aliased_prefix
-
-
1
attr_reader :tables
-
-
1
delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
-
1
delegate :table, :table_name, :to => :parent, :prefix => :parent
-
1
delegate :alias_tracker, :to => :join_dependency
-
-
1
alias :alias_suffix :parent_table_name
-
-
1
def initialize(reflection, join_dependency, parent = nil)
-
reflection.check_validity!
-
-
if reflection.options[:polymorphic]
-
raise EagerLoadPolymorphicError.new(reflection)
-
end
-
-
super(reflection.klass)
-
-
@reflection = reflection
-
@join_dependency = join_dependency
-
@parent = parent
-
@join_type = Arel::InnerJoin
-
@aliased_prefix = "t#{ join_dependency.join_parts.size }"
-
@tables = construct_tables.reverse
-
end
-
-
1
def ==(other)
-
other.class == self.class &&
-
other.reflection == reflection &&
-
other.parent == parent
-
end
-
-
1
def find_parent_in(other_join_dependency)
-
other_join_dependency.join_parts.detect do |join_part|
-
case parent
-
when JoinBase
-
parent.base_klass == join_part.base_klass
-
else
-
parent == join_part
-
end
-
end
-
end
-
-
1
def join_to(manager)
-
tables = @tables.dup
-
foreign_table = parent_table
-
foreign_klass = parent.base_klass
-
-
# The chain starts with the target table, but we want to end with it here (makes
-
# more sense in this context), so we reverse
-
chain.reverse.each_with_index do |reflection, i|
-
table = tables.shift
-
-
case reflection.source_macro
-
when :belongs_to
-
key = reflection.association_primary_key
-
foreign_key = reflection.foreign_key
-
when :has_and_belongs_to_many
-
# Join the join table first...
-
manager.from(join(
-
table,
-
table[reflection.foreign_key].
-
eq(foreign_table[reflection.active_record_primary_key])
-
))
-
-
foreign_table, table = table, tables.shift
-
-
key = reflection.association_primary_key
-
foreign_key = reflection.association_foreign_key
-
else
-
key = reflection.foreign_key
-
foreign_key = reflection.active_record_primary_key
-
end
-
-
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
-
-
scope_chain_items = scope_chain[i]
-
-
if reflection.type
-
scope_chain_items += [
-
ActiveRecord::Relation.new(reflection.klass, table)
-
.where(reflection.type => foreign_klass.base_class.name)
-
]
-
end
-
-
scope_chain_items += [reflection.klass.send(:build_default_scope)].compact
-
-
scope_chain_items.each do |item|
-
unless item.is_a?(Relation)
-
item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
-
end
-
-
constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
-
end
-
-
manager.from(join(table, constraint))
-
-
# The current table in this iteration becomes the foreign table in the next
-
foreign_table, foreign_klass = table, reflection.klass
-
end
-
-
manager
-
end
-
-
# Builds equality condition.
-
#
-
# Example:
-
#
-
# class Physician < ActiveRecord::Base
-
# has_many :appointments
-
# end
-
#
-
# If I execute `Physician.joins(:appointments).to_a` then
-
# reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
-
# table #=> #<Arel::Table @name="appointments" ...>
-
# key #=> physician_id
-
# foreign_table #=> #<Arel::Table @name="physicians" ...>
-
# foreign_key #=> id
-
#
-
1
def build_constraint(reflection, table, key, foreign_table, foreign_key)
-
constraint = table[key].eq(foreign_table[foreign_key])
-
-
if reflection.klass.finder_needs_type_condition?
-
constraint = table.create_and([
-
constraint,
-
reflection.klass.send(:type_condition, table)
-
])
-
end
-
-
constraint
-
end
-
-
1
def join_relation(joining_relation)
-
self.join_type = Arel::OuterJoin
-
joining_relation.joins(self)
-
end
-
-
1
def table
-
tables.last
-
end
-
-
1
def aliased_table_name
-
table.table_alias || table.name
-
end
-
-
1
def scope_chain
-
@scope_chain ||= reflection.scope_chain.reverse
-
end
-
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
1
class JoinBase < JoinPart # :nodoc:
-
1
def ==(other)
-
other.class == self.class &&
-
other.base_klass == base_klass
-
end
-
-
1
def aliased_prefix
-
4887
"t0"
-
end
-
-
1
def table
-
554
Arel::Table.new(table_name, arel_engine)
-
end
-
-
1
def aliased_table_name
-
554
base_klass.table_name
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
# A JoinPart represents a part of a JoinDependency. It is inherited
-
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
-
# everything else is being joined onto. A JoinAssociation represents an association which
-
# is joining to the base. A JoinAssociation may result in more than one actual join
-
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
-
# two; one for the join table and one for the target table).
-
1
class JoinPart # :nodoc:
-
# The Active Record class which this join part is associated 'about'; for a JoinBase
-
# this is the actual base model, for a JoinAssociation this is the target model of the
-
# association.
-
1
attr_reader :base_klass
-
-
1
delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
-
-
1
def initialize(base_klass)
-
1028
@base_klass = base_klass
-
1028
@cached_record = {}
-
1028
@column_names_with_alias = nil
-
end
-
-
1
def aliased_table
-
554
Arel::Nodes::TableAlias.new table, aliased_table_name
-
end
-
-
1
def ==(other)
-
raise NotImplementedError
-
end
-
-
# An Arel::Table for the active_record
-
1
def table
-
raise NotImplementedError
-
end
-
-
# The prefix to be used when aliasing columns in the active_record's table
-
1
def aliased_prefix
-
raise NotImplementedError
-
end
-
-
# The alias for the active_record's table
-
1
def aliased_table_name
-
raise NotImplementedError
-
end
-
-
# The alias for the primary key of the active_record's table
-
1
def aliased_primary_key
-
"#{aliased_prefix}_r0"
-
end
-
-
# An array of [column_name, alias] pairs for the table
-
1
def column_names_with_alias
-
554
unless @column_names_with_alias
-
554
@column_names_with_alias = []
-
-
554
([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i|
-
4887
@column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
-
end
-
end
-
554
@column_names_with_alias
-
end
-
-
1
def extract_record(row)
-
# This code is performance critical as it is called per row.
-
# see: https://github.com/rails/rails/pull/12185
-
hash = {}
-
-
index = 0
-
length = column_names_with_alias.length
-
-
while index < length
-
column_name, alias_name = column_names_with_alias[index]
-
hash[column_name] = row[alias_name]
-
index += 1
-
end
-
-
hash
-
end
-
-
1
def record_id(row)
-
row[aliased_primary_key]
-
end
-
-
1
def instantiate(row)
-
@cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
-
1
module JoinHelper #:nodoc:
-
-
1
def join_type
-
225
Arel::InnerJoin
-
end
-
-
1
private
-
-
1
def construct_tables
-
496
tables = []
-
496
chain.each do |reflection|
-
tables << alias_tracker.aliased_table_for(
-
table_name_for(reflection),
-
table_alias_for(reflection, reflection != self.reflection)
-
721
)
-
-
721
if reflection.source_macro == :has_and_belongs_to_many
-
tables << alias_tracker.aliased_table_for(
-
(reflection.source_reflection || reflection).join_table,
-
table_alias_for(reflection, true)
-
)
-
end
-
end
-
496
tables
-
end
-
-
1
def table_name_for(reflection)
-
reflection.table_name
-
end
-
-
1
def table_alias_for(reflection, join = false)
-
721
name = "#{reflection.plural_name}_#{alias_suffix}"
-
721
name << "_join" if join
-
721
name
-
end
-
-
1
def join(table, constraint)
-
225
table.create_join(table, table.create_on(constraint), join_type)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class SingularAssociation < Association #:nodoc:
-
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
-
1
def reader(force_reload = false)
-
260
if force_reload
-
klass.uncached { reload }
-
elsif !loaded? || stale_target?
-
73
reload
-
end
-
-
260
target
-
end
-
-
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
-
1
def writer(record)
-
44
replace(record)
-
end
-
-
1
def create(attributes = {}, &block)
-
create_record(attributes, &block)
-
end
-
-
1
def create!(attributes = {}, &block)
-
create_record(attributes, true, &block)
-
end
-
-
1
def build(attributes = {})
-
record = build_record(attributes)
-
yield(record) if block_given?
-
set_new_record(record)
-
record
-
end
-
-
1
private
-
-
1
def create_scope
-
scope.scope_for_create.stringify_keys.except(klass.primary_key)
-
end
-
-
1
def find_target
-
146
scope.take.tap { |record| set_inverse_instance(record) }
-
end
-
-
# Implemented by subclasses
-
1
def replace(record)
-
raise NotImplementedError, "Subclasses must implement a replace(record) method"
-
end
-
-
1
def set_new_record(record)
-
replace(record)
-
end
-
-
1
def create_record(attributes, raise_error = false)
-
record = build_record(attributes)
-
yield(record) if block_given?
-
saved = record.save
-
set_new_record(record)
-
raise RecordInvalid.new(record) if !saved && raise_error
-
record
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Through Association
-
1
module Associations
-
1
module ThroughAssociation #:nodoc:
-
-
1
delegate :source_reflection, :through_reflection, :chain, :to => :reflection
-
-
1
protected
-
-
# We merge in these scopes for two reasons:
-
#
-
# 1. To get the default_scope conditions for any of the other reflections in the chain
-
# 2. To get the type conditions for any STI models in the chain
-
1
def target_scope
-
467
scope = super
-
467
chain[1..-1].each do |reflection|
-
467
scope.merge!(
-
reflection.klass.all.with_default_scope.
-
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
-
)
-
end
-
467
scope
-
end
-
-
1
private
-
-
# Construct attributes for :through pointing to owner and associate. This is used by the
-
# methods which create and delete records on the association.
-
#
-
# We only support indirectly modifying through associations which has a belongs_to source.
-
# This is the "has_many :tags, through: :taggings" situation, where the join model
-
# typically has a belongs_to on both side. In other words, associations which could also
-
# be represented as has_and_belongs_to_many associations.
-
#
-
# We do not support creating/deleting records on the association where the source has
-
# some other type, because this opens up a whole can of worms, and in basically any
-
# situation it is more natural for the user to just create or modify their join records
-
# directly as required.
-
1
def construct_join_attributes(*records)
-
ensure_mutable
-
-
join_attributes = {
-
source_reflection.foreign_key =>
-
records.map { |record|
-
record.send(source_reflection.association_primary_key(reflection.klass))
-
}
-
}
-
-
if options[:source_type]
-
join_attributes[source_reflection.foreign_type] =
-
records.map { |record| record.class.base_class.name }
-
end
-
-
if records.count == 1
-
Hash[join_attributes.map { |k, v| [k, v.first] }]
-
else
-
join_attributes
-
end
-
end
-
-
# Note: this does not capture all cases, for example it would be crazy to try to
-
# properly support stale-checking for nested associations.
-
1
def stale_state
-
1
if through_reflection.macro == :belongs_to
-
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
-
end
-
end
-
-
1
def foreign_key_present?
-
through_reflection.macro == :belongs_to &&
-
!owner[through_reflection.foreign_key].nil?
-
end
-
-
1
def ensure_mutable
-
if source_reflection.macro != :belongs_to
-
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
-
end
-
end
-
-
1
def ensure_not_nested
-
if reflection.nested?
-
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
-
end
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module AttributeAssignment
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::DeprecatedMassAssignmentSecurity
-
1
include ActiveModel::ForbiddenAttributesProtection
-
-
# Allows you to set all the attributes by passing in a hash of attributes with
-
# keys matching the attribute names (which again matches the column names).
-
#
-
# If the passed hash responds to <tt>permitted?</tt> method and the return value
-
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
-
# exception is raised.
-
1
def assign_attributes(new_attributes)
-
365
return if new_attributes.blank?
-
-
295
attributes = new_attributes.stringify_keys
-
295
multi_parameter_attributes = []
-
295
nested_parameter_attributes = []
-
-
295
attributes = sanitize_for_mass_assignment(attributes)
-
-
295
attributes.each do |k, v|
-
597
if k.include?("(")
-
multi_parameter_attributes << [ k, v ]
-
elsif v.is_a?(Hash)
-
nested_parameter_attributes << [ k, v ]
-
else
-
597
_assign_attribute(k, v)
-
end
-
end
-
-
295
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
-
295
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
-
end
-
-
1
alias attributes= assign_attributes
-
-
1
private
-
-
1
def _assign_attribute(k, v)
-
597
public_send("#{k}=", v)
-
rescue NoMethodError
-
if respond_to?("#{k}=")
-
raise
-
else
-
raise UnknownAttributeError, "unknown attribute: #{k}"
-
end
-
end
-
-
# Assign any deferred nested attributes after the base attributes have been set.
-
1
def assign_nested_parameter_attributes(pairs)
-
pairs.each { |k, v| _assign_attribute(k, v) }
-
end
-
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
-
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
-
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
-
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
-
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
-
1
def assign_multiparameter_attributes(pairs)
-
execute_callstack_for_multiparameter_attributes(
-
extract_callstack_for_multiparameter_attributes(pairs)
-
)
-
end
-
-
1
def execute_callstack_for_multiparameter_attributes(callstack)
-
errors = []
-
callstack.each do |name, values_with_empty_parameters|
-
begin
-
send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
-
rescue => ex
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
-
end
-
end
-
unless errors.empty?
-
error_descriptions = errors.map { |ex| ex.message }.join(",")
-
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
-
end
-
end
-
-
1
def extract_callstack_for_multiparameter_attributes(pairs)
-
attributes = {}
-
-
pairs.each do |(multiparameter_name, value)|
-
attribute_name = multiparameter_name.split("(").first
-
attributes[attribute_name] ||= {}
-
-
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
-
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
-
end
-
-
attributes
-
end
-
-
1
def type_cast_attribute_value(multiparameter_name, value)
-
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
-
end
-
-
1
def find_parameter_position(multiparameter_name)
-
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
-
end
-
-
1
class MultiparameterAttribute #:nodoc:
-
1
attr_reader :object, :name, :values, :column
-
-
1
def initialize(object, name, values)
-
@object = object
-
@name = name
-
@values = values
-
end
-
-
1
def read_value
-
return if values.values.compact.empty?
-
-
@column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
-
klass = column.klass
-
-
if klass == Time
-
read_time
-
elsif klass == Date
-
read_date
-
else
-
read_other(klass)
-
end
-
end
-
-
1
private
-
-
1
def instantiate_time_object(set_values)
-
if object.class.send(:create_time_zone_conversion_attribute?, name, column)
-
Time.zone.local(*set_values)
-
else
-
Time.send(object.class.default_timezone, *set_values)
-
end
-
end
-
-
1
def read_time
-
# If column is a :time (and not :date or :timestamp) there is no need to validate if
-
# there are year/month/day fields
-
if column.type == :time
-
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
-
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
-
values[key] ||= value
-
end
-
else
-
# else column is a timestamp, so if Date bits were not provided, error
-
validate_required_parameters!([1,2,3])
-
-
# If Date bits were provided but blank, then return nil
-
return if blank_date_parameter?
-
end
-
-
max_position = extract_max_param(6)
-
set_values = values.values_at(*(1..max_position))
-
# If Time bits are not there, then default to 0
-
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
-
instantiate_time_object(set_values)
-
end
-
-
1
def read_date
-
return if blank_date_parameter?
-
set_values = values.values_at(1,2,3)
-
begin
-
Date.new(*set_values)
-
rescue ArgumentError # if Date.new raises an exception on an invalid date
-
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
-
end
-
end
-
-
1
def read_other(klass)
-
max_position = extract_max_param
-
positions = (1..max_position)
-
validate_required_parameters!(positions)
-
-
set_values = values.values_at(*positions)
-
klass.new(*set_values)
-
end
-
-
# Checks whether some blank date parameter exists. Note that this is different
-
# than the validate_required_parameters! method, since it just checks for blank
-
# positions instead of missing ones, and does not raise in case one blank position
-
# exists. The caller is responsible to handle the case of this returning true.
-
1
def blank_date_parameter?
-
(1..3).any? { |position| values[position].blank? }
-
end
-
-
# If some position is not provided, it errors out a missing parameter exception.
-
1
def validate_required_parameters!(positions)
-
if missing_parameter = positions.detect { |position| !values.key?(position) }
-
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
-
end
-
end
-
-
1
def extract_max_param(upper_cap = 100)
-
[values.keys.max, upper_cap].min
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
# = Active Record Attribute Methods Before Type Cast
-
#
-
# <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
-
# read the value of the attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.id # => 1
-
# task.completed_on # => Sun, 21 Oct 2012
-
#
-
# task.attributes_before_type_cast
-
# # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
-
# task.read_attribute_before_type_cast('id') # => "1"
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
#
-
# In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
-
# it declares a method for all attributes with the <tt>*_before_type_cast</tt>
-
# suffix.
-
#
-
# task.id_before_type_cast # => "1"
-
# task.completed_on_before_type_cast # => "2012-10-21"
-
1
module BeforeTypeCast
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "_before_type_cast"
-
end
-
-
# Returns the value of the attribute identified by +attr_name+ before
-
# typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.read_attribute('id') # => 1
-
# task.read_attribute_before_type_cast('id') # => '1'
-
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
1
def read_attribute_before_type_cast(attr_name)
-
184
@attributes[attr_name]
-
end
-
-
# Returns a hash of attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
-
# task.attributes
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
-
# task.attributes_before_type_cast
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
-
1
def attributes_before_type_cast
-
@attributes
-
end
-
-
1
private
-
-
# Handle *_before_type_cast for method_missing.
-
1
def attribute_before_type_cast(attribute_name)
-
175
read_attribute_before_type_cast(attribute_name)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Dirty # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
include ActiveModel::Dirty
-
-
1
included do
-
1
if self < ::ActiveRecord::Timestamp
-
raise "You cannot include Dirty after Timestamp"
-
end
-
-
1
class_attribute :partial_writes, instance_writer: false
-
1
self.partial_writes = true
-
-
1
def self.partial_updates=(v); self.partial_writes = v; end
-
1
def self.partial_updates?; partial_writes?; end
-
1
def self.partial_updates; partial_writes; end
-
-
1
ActiveSupport::Deprecation.deprecate_methods(
-
singleton_class,
-
:partial_updates= => :partial_writes=,
-
:partial_updates? => :partial_writes?,
-
:partial_updates => :partial_writes
-
)
-
end
-
-
# Attempts to +save+ the record and clears changed attributes if successful.
-
1
def save(*)
-
136
if status = super
-
124
@previously_changed = changes
-
124
@changed_attributes.clear
-
end
-
136
status
-
end
-
-
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
-
1
def save!(*)
-
263
super.tap do
-
263
@previously_changed = changes
-
263
@changed_attributes.clear
-
end
-
end
-
-
# <tt>reload</tt> the record and clears changed attributes.
-
1
def reload(*)
-
4
super.tap do
-
4
@previously_changed.clear
-
4
@changed_attributes.clear
-
end
-
end
-
-
1
private
-
# Wrap write_attribute to remember original attribute value.
-
1
def write_attribute(attr, value)
-
2981
attr = attr.to_s
-
-
# The attribute already has an unsaved change.
-
2981
if attribute_changed?(attr)
-
327
old = @changed_attributes[attr]
-
327
@changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
-
else
-
2654
old = clone_attribute_value(:read_attribute, attr)
-
2654
@changed_attributes[attr] = old if _field_changed?(attr, old, value)
-
end
-
-
# Carry on.
-
2981
super(attr, value)
-
end
-
-
1
def update_record(*)
-
96
partial_writes? ? super(keys_for_partial_write) : super
-
end
-
-
1
def create_record(*)
-
291
partial_writes? ? super(keys_for_partial_write) : super
-
end
-
-
# Serialized attributes should always be written in case they've been
-
# changed in place.
-
1
def keys_for_partial_write
-
387
changed | (attributes.keys & self.class.serialized_attributes.keys)
-
end
-
-
1
def _field_changed?(attr, old, value)
-
6436
if column = column_for_attribute(attr)
-
if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
-
6436
changes_from_zero_to_string?(old, value))
-
140
value = nil
-
else
-
6296
value = column.type_cast(value)
-
end
-
end
-
-
6436
old != value
-
end
-
-
1
def changes_from_nil_to_empty_string?(column, old, value)
-
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
-
# Hence we don't record it as a change if the value changes from nil to ''.
-
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
-
# be typecast back to 0 (''.to_i => 0)
-
1514
column.null && (old.nil? || old == 0) && value.blank?
-
end
-
-
1
def changes_from_zero_to_string?(old, value)
-
# For columns with old 0 and value non-empty string
-
1374
old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
-
end
-
-
1
def non_zero?(value)
-
value !~ /\A0+(\.0+)?\z/
-
end
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module PrimaryKey
-
1
extend ActiveSupport::Concern
-
-
# Returns this record's primary key value wrapped in an Array if one is
-
# available.
-
1
def to_key
-
119
sync_with_transaction_state
-
119
key = self.id
-
119
[key] if key
-
end
-
-
# Returns the primary key value.
-
1
def id
-
sync_with_transaction_state
-
read_attribute(self.class.primary_key)
-
end
-
-
# Sets the primary key value.
-
1
def id=(value)
-
sync_with_transaction_state
-
write_attribute(self.class.primary_key, value) if self.class.primary_key
-
end
-
-
# Queries the primary key value.
-
1
def id?
-
sync_with_transaction_state
-
query_attribute(self.class.primary_key)
-
end
-
-
# Returns the primary key value before type cast.
-
1
def id_before_type_cast
-
sync_with_transaction_state
-
read_attribute_before_type_cast(self.class.primary_key)
-
end
-
-
1
protected
-
-
1
def attribute_method?(attr_name)
-
680
attr_name == 'id' || super
-
end
-
-
1
module ClassMethods
-
1
def define_method_attribute(attr_name)
-
27
super
-
-
27
if attr_name == primary_key && attr_name != 'id'
-
generated_attribute_methods.send(:alias_method, :id, primary_key)
-
end
-
end
-
-
1
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
-
-
1
def dangerous_attribute_method?(method_name)
-
243
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
-
end
-
-
# Defines the primary key field -- can be overridden in subclasses.
-
# Overwriting will negate any effect of the +primary_key_prefix_type+
-
# setting, though.
-
1
def primary_key
-
6663
@primary_key = reset_primary_key unless defined? @primary_key
-
6663
@primary_key
-
end
-
-
# Returns a quoted version of the primary key name, used to construct
-
# SQL statements.
-
1
def quoted_primary_key
-
@quoted_primary_key ||= connection.quote_column_name(primary_key)
-
end
-
-
1
def reset_primary_key #:nodoc:
-
5
if self == base_class
-
5
self.primary_key = get_primary_key(base_class.name)
-
else
-
self.primary_key = base_class.primary_key
-
end
-
end
-
-
1
def get_primary_key(base_name) #:nodoc:
-
5
return 'id' if base_name.blank?
-
-
5
case primary_key_prefix_type
-
when :table_name
-
base_name.foreign_key(false)
-
when :table_name_with_underscore
-
base_name.foreign_key
-
else
-
5
if ActiveRecord::Base != self && table_exists?
-
5
connection.schema_cache.primary_keys(table_name)
-
else
-
'id'
-
end
-
end
-
end
-
-
# Sets the name of the primary key column.
-
#
-
# class Project < ActiveRecord::Base
-
# self.primary_key = 'sysid'
-
# end
-
#
-
# You can also define the +primary_key+ method yourself:
-
#
-
# class Project < ActiveRecord::Base
-
# def self.primary_key
-
# 'foo_' + super
-
# end
-
# end
-
#
-
# Project.primary_key # => "foo_id"
-
1
def primary_key=(value)
-
5
@primary_key = value && value.to_s
-
5
@quoted_primary_key = nil
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Query
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "?"
-
end
-
-
1
def query_attribute(attr_name)
-
76
value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
-
-
76
case value
-
5
when true then true
-
71
when false, nil then false
-
else
-
column = self.class.columns_hash[attr_name]
-
if column.nil?
-
if Numeric === value || value !~ /[^0-9]/
-
!value.to_i.zero?
-
else
-
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
-
!value.blank?
-
end
-
elsif column.number?
-
!value.zero?
-
else
-
!value.blank?
-
end
-
end
-
end
-
-
1
private
-
# Handle *? for method_missing.
-
1
def attribute?(attribute_name)
-
76
query_attribute(attribute_name)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Read
-
1
extend ActiveSupport::Concern
-
-
1
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
-
-
1
included do
-
1
class_attribute :attribute_types_cached_by_default, instance_writer: false
-
1
self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
end
-
-
1
module ClassMethods
-
# +cache_attributes+ allows you to declare which converted attribute
-
# values should be cached. Usually caching only pays off for attributes
-
# with expensive conversion methods, like time related columns (e.g.
-
# +created_at+, +updated_at+).
-
1
def cache_attributes(*attribute_names)
-
cached_attributes.merge attribute_names.map { |attr| attr.to_s }
-
end
-
-
# Returns the attributes which are cached. By default time related columns
-
# with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
-
1
def cached_attributes
-
7439
@cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
-
end
-
-
# Returns +true+ if the provided attribute is being cached.
-
1
def cache_attribute?(attr_name)
-
7385
cached_attributes.include?(attr_name)
-
end
-
-
1
protected
-
-
# We want to generate the methods via module_eval rather than
-
# define_method, because define_method is slower on dispatch.
-
# Evaluating many similar methods may use more memory as the instruction
-
# sequences are duplicated and cached (in MRI). define_method may
-
# be slower on dispatch, but if you're careful about the closure
-
# created, then define_method will consume much less memory.
-
#
-
# But sometimes the database might return columns with
-
# characters that are not allowed in normal method names (like
-
# 'my_column(omg)'. So to work around this we first define with
-
# the __temp__ identifier, and then use alias method to rename
-
# it to what we want.
-
#
-
# We are also defining a constant to hold the frozen string of
-
# the attribute name. Using a constant means that we do not have
-
# to allocate an object on each call to the attribute method.
-
# Making it frozen means that it doesn't get duped when used to
-
# key the @attributes_cache in read_attribute.
-
1
def define_method_attribute(name)
-
27
safe_name = name.unpack('h*').first
-
27
generated_attribute_methods::AttrNames.set_name_cache safe_name, name
-
-
27
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
-
def __temp__#{safe_name}
-
read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
-
end
-
alias_method #{name.inspect}, :__temp__#{safe_name}
-
undef_method :__temp__#{safe_name}
-
STR
-
end
-
-
1
private
-
-
1
def cacheable_column?(column)
-
27
if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
27
! serialized_attributes.include? column.name
-
else
-
attribute_types_cached_by_default.include?(column.type)
-
end
-
end
-
end
-
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after
-
# it has been typecast (for example, "2004-12-12" in a data column is cast
-
# to a date object, like Date.new(2004, 12, 12)).
-
1
def read_attribute(attr_name)
-
# If it's cached, just return it
-
# We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
-
21221
name = attr_name.to_s
-
@attributes_cache[name] || @attributes_cache.fetch(name) {
-
7386
column = @column_types_override[name] if @column_types_override
-
7386
column ||= @column_types[name]
-
-
return @attributes.fetch(name) {
-
1
if name == 'id' && self.class.primary_key != name
-
read_attribute(self.class.primary_key)
-
end
-
7386
} unless column
-
-
7385
value = @attributes.fetch(name) {
-
return block_given? ? yield(name) : nil
-
}
-
-
7385
if self.class.cache_attribute?(name)
-
7385
@attributes_cache[name] = column.type_cast(value)
-
else
-
column.type_cast value
-
end
-
21221
}
-
end
-
-
1
private
-
-
1
def attribute(attribute_name)
-
read_attribute(attribute_name)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Serialization
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Returns a hash of all the attributes that have been specified for
-
# serialization as keys and their class restriction as values.
-
1
class_attribute :serialized_attributes, instance_accessor: false
-
1
self.serialized_attributes = {}
-
end
-
-
1
module ClassMethods
-
##
-
# :method: serialized_attributes
-
#
-
# Returns a hash of all the attributes that have been specified for
-
# serialization as keys and their class restriction as values.
-
-
# If you have an attribute that needs to be saved to the database as an
-
# object, and retrieved as the same object, then specify the name of that
-
# attribute using this method and it will be handled automatically. The
-
# serialization is done through YAML. If +class_name+ is specified, the
-
# serialized object must be of that class on retrieval or
-
# <tt>SerializationTypeMismatch</tt> will be raised.
-
#
-
# ==== Parameters
-
#
-
# * +attr_name+ - The field name that should be serialized.
-
# * +class_name+ - Optional, class name that the object type should be equal to.
-
#
-
# ==== Example
-
#
-
# # Serialize a preferences attribute.
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
1
def serialize(attr_name, class_name = Object)
-
include Behavior
-
-
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
-
class_name
-
else
-
Coders::YAMLColumn.new(class_name)
-
end
-
-
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
-
# has its own hash of own serialized attributes
-
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
-
end
-
end
-
-
# *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
-
1
def serialized_attributes
-
message = "Instance level serialized_attributes method is deprecated, please use class level method."
-
ActiveSupport::Deprecation.warn message
-
defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
-
end
-
-
1
class Type # :nodoc:
-
1
def initialize(column)
-
@column = column
-
end
-
-
1
def type_cast(value)
-
if value.state == :serialized
-
value.unserialized_value @column.type_cast value.value
-
else
-
value.unserialized_value
-
end
-
end
-
-
1
def type
-
@column.type
-
end
-
end
-
-
1
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
-
1
def unserialized_value(v = value)
-
state == :serialized ? unserialize(v) : value
-
end
-
-
1
def serialized_value
-
state == :unserialized ? serialize : value
-
end
-
-
1
def unserialize(v)
-
self.state = :unserialized
-
self.value = coder.load(v)
-
end
-
-
1
def serialize
-
self.state = :serialized
-
self.value = coder.dump(value)
-
end
-
end
-
-
# This is only added to the model when serialize is called, which
-
# ensures we do not make things slower when serialization is not used.
-
1
module Behavior # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods # :nodoc:
-
1
def initialize_attributes(attributes, options = {})
-
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
-
super(attributes, options)
-
-
serialized_attributes.each do |key, coder|
-
if attributes.key?(key)
-
attributes[key] = Attribute.new(coder, attributes[key], serialized)
-
end
-
end
-
-
attributes
-
end
-
end
-
-
1
def type_cast_attribute_for_write(column, value)
-
if column && coder = self.class.serialized_attributes[column.name]
-
Attribute.new(coder, value, :unserialized)
-
else
-
super
-
end
-
end
-
-
1
def _field_changed?(attr, old, value)
-
if self.class.serialized_attributes.include?(attr)
-
old != value
-
else
-
super
-
end
-
end
-
-
1
def read_attribute_before_type_cast(attr_name)
-
if self.class.serialized_attributes.include?(attr_name)
-
super.unserialized_value
-
else
-
super
-
end
-
end
-
-
1
def attributes_before_type_cast
-
super.dup.tap do |attributes|
-
self.class.serialized_attributes.each_key do |key|
-
if attributes.key?(key)
-
attributes[key] = attributes[key].unserialized_value
-
end
-
end
-
end
-
end
-
-
1
def typecasted_attribute_value(name)
-
if self.class.serialized_attributes.include?(name)
-
@attributes[name].serialized_value
-
else
-
super
-
end
-
end
-
-
1
def attributes_for_coder
-
attribute_names.each_with_object({}) do |name, attrs|
-
attrs[name] = if self.class.serialized_attributes.include?(name)
-
@attributes[name].serialized_value
-
else
-
read_attribute(name)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module TimeZoneConversion
-
1
class Type # :nodoc:
-
1
def initialize(column)
-
8
@column = column
-
end
-
-
1
def type_cast(value)
-
1420
value = @column.type_cast(value)
-
1420
value.acts_like?(:time) ? value.in_time_zone : value
-
end
-
-
1
def type
-
@column.type
-
end
-
end
-
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
mattr_accessor :time_zone_aware_attributes, instance_writer: false
-
1
self.time_zone_aware_attributes = false
-
-
1
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
-
1
self.skip_time_zone_conversion_for_attributes = []
-
end
-
-
1
module ClassMethods
-
1
protected
-
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
-
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
-
1
def define_method_attribute=(attr_name)
-
27
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
-
8
method_body, line = <<-EOV, __LINE__ + 1
-
def #{attr_name}=(time)
-
time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
-
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
-
write_attribute(:#{attr_name}, time)
-
#{attr_name}_will_change! if previous_time != time_with_zone
-
@attributes_cache["#{attr_name}"] = time_with_zone
-
end
-
EOV
-
8
generated_attribute_methods.module_eval(method_body, __FILE__, line)
-
else
-
19
super
-
end
-
end
-
-
1
private
-
1
def create_time_zone_conversion_attribute?(name, column)
-
time_zone_aware_attributes &&
-
54
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
-
54
(:datetime == column.type || :timestamp == column.type)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Write
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "="
-
end
-
-
1
module ClassMethods
-
1
protected
-
-
# See define_method_attribute in read.rb for an explanation of
-
# this code.
-
1
def define_method_attribute=(name)
-
19
safe_name = name.unpack('h*').first
-
19
generated_attribute_methods::AttrNames.set_name_cache safe_name, name
-
-
19
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
-
def __temp__#{safe_name}=(value)
-
write_attribute(AttrNames::ATTR_#{safe_name}, value)
-
end
-
19
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
-
undef_method :__temp__#{safe_name}=
-
STR
-
end
-
end
-
-
# Updates the attribute identified by <tt>attr_name</tt> with the
-
# specified +value+. Empty strings for fixnum and float columns are
-
# turned into +nil+.
-
1
def write_attribute(attr_name, value)
-
2981
attr_name = attr_name.to_s
-
2981
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
-
2981
@attributes_cache.delete(attr_name)
-
2981
column = column_for_attribute(attr_name)
-
-
# If we're dealing with a binary column, write the data to the cache
-
# so we don't attempt to typecast multiple times.
-
2981
if column && column.binary?
-
@attributes_cache[attr_name] = value
-
end
-
-
2981
if column || @attributes.has_key?(attr_name)
-
2981
@attributes[attr_name] = type_cast_attribute_for_write(column, value)
-
else
-
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
-
end
-
end
-
1
alias_method :raw_write_attribute, :write_attribute
-
-
1
private
-
# Handle *= for method_missing.
-
1
def attribute=(attribute_name, value)
-
write_attribute(attribute_name, value)
-
end
-
-
1
def type_cast_attribute_for_write(column, value)
-
2981
return value unless column
-
-
2981
column.type_cast_for_write value
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Autosave Association
-
#
-
# +AutosaveAssociation+ is a module that takes care of automatically saving
-
# associated records when their parent is saved. In addition to saving, it
-
# also destroys any associated records that were marked for destruction.
-
# (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
-
#
-
# Saving of the parent, its associations, and the destruction of marked
-
# associations, all happen inside a transaction. This should never leave the
-
# database in an inconsistent state.
-
#
-
# If validations for any of the associations fail, their error messages will
-
# be applied to the parent.
-
#
-
# Note that it also means that associations marked for destruction won't
-
# be destroyed directly. They will however still be marked for destruction.
-
#
-
# Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
-
# When the <tt>:autosave</tt> option is not present then new association records are
-
# saved but the updated association records are not saved.
-
#
-
# == Validation
-
#
-
# Children records are validated unless <tt>:validate</tt> is +false+.
-
#
-
# == Callbacks
-
#
-
# Association with autosave option defines several callbacks on your
-
# model (before_save, after_create, after_update). Please note that
-
# callbacks are executed in the order they were defined in
-
# model. You should avoid modifying the association content, before
-
# autosave callbacks are executed. Placing your callbacks after
-
# associations is usually a good practice.
-
#
-
# === One-to-one Example
-
#
-
# class Post
-
# has_one :author, autosave: true
-
# end
-
#
-
# Saving changes to the parent and its associated model can now be performed
-
# automatically _and_ atomically:
-
#
-
# post = Post.find(1)
-
# post.title # => "The current global position of migrating ducks"
-
# post.author.name # => "alloy"
-
#
-
# post.title = "On the migration of ducks"
-
# post.author.name = "Eloy Duran"
-
#
-
# post.save
-
# post.reload
-
# post.title # => "On the migration of ducks"
-
# post.author.name # => "Eloy Duran"
-
#
-
# Destroying an associated model, as part of the parent's save action, is as
-
# simple as marking it for destruction:
-
#
-
# post.author.mark_for_destruction
-
# post.author.marked_for_destruction? # => true
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.author.id
-
# Author.find_by(id: id).nil? # => false
-
#
-
# post.save
-
# post.reload.author # => nil
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Author.find_by(id: id).nil? # => true
-
#
-
# === One-to-many Example
-
#
-
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
-
#
-
# class Post
-
# has_many :comments # :autosave option is not declared
-
# end
-
#
-
# post = Post.new(title: 'ruby rocks')
-
# post.comments.build(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# post.comments.build(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# post.comments.create(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
-
# are new records or not:
-
#
-
# class Post
-
# has_many :comments, autosave: true
-
# end
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# post.comments.create(body: 'hello world')
-
# post.comments[0].body = 'hi everyone'
-
# post.save # => saves both post and comment, with 'hi everyone' as body
-
#
-
# Destroying one of the associated models as part of the parent's save action
-
# is as simple as marking it for destruction:
-
#
-
# post.comments.last.mark_for_destruction
-
# post.comments.last.marked_for_destruction? # => true
-
# post.comments.length # => 2
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.comments.last.id
-
# Comment.find_by(id: id).nil? # => false
-
#
-
# post.save
-
# post.reload.comments.length # => 1
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Comment.find_by(id: id).nil? # => true
-
-
1
module AutosaveAssociation
-
1
extend ActiveSupport::Concern
-
-
1
module AssociationBuilderExtension #:nodoc:
-
1
def build
-
14
model.send(:add_autosave_association_callbacks, reflection)
-
14
super
-
end
-
end
-
-
1
included do
-
1
Associations::Builder::Association.class_eval do
-
1
self.valid_options << :autosave
-
1
include AssociationBuilderExtension
-
end
-
end
-
-
1
module ClassMethods
-
1
private
-
-
1
def define_non_cyclic_method(name, reflection, &block)
-
22
define_method(name) do |*args|
-
4640
result = true; @_already_called ||= {}
-
# Loop prevention for validation of associations
-
4640
unless @_already_called[[name, reflection.name]]
-
4640
begin
-
4640
@_already_called[[name, reflection.name]]=true
-
4640
result = instance_eval(&block)
-
ensure
-
4640
@_already_called[[name, reflection.name]]=false
-
end
-
end
-
-
4640
result
-
end
-
end
-
-
# Adds validation and save callbacks for the association as specified by
-
# the +reflection+.
-
#
-
# For performance reasons, we don't check whether to validate at runtime.
-
# However the validation and callback methods are lazy and those methods
-
# get created when they are invoked for the very first time. However,
-
# this can change, for instance, when using nested attributes, which is
-
# called _after_ the association has been defined. Since we don't want
-
# the callbacks to get defined multiple times, there are guards that
-
# check if the save or validation methods have already been defined
-
# before actually defining them.
-
1
def add_autosave_association_callbacks(reflection)
-
14
save_method = :"autosave_associated_records_for_#{reflection.name}"
-
14
validation_method = :"validate_associated_records_for_#{reflection.name}"
-
14
collection = reflection.collection?
-
-
14
unless method_defined?(save_method)
-
14
if collection
-
8
before_save :before_save_collection_association
-
-
2400
define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
-
# Doesn't use after_save as that would save associations added in after_create/after_update twice
-
8
after_create save_method
-
8
after_update save_method
-
elsif reflection.macro == :has_one
-
define_method(save_method) { save_has_one_association(reflection) }
-
# Configures two callbacks instead of a single after_save so that
-
# the model may rely on their execution order relative to its
-
# own callbacks.
-
#
-
# For example, given that after_creates run before after_saves, if
-
# we configured instead an after_save there would be no way to fire
-
# a custom after_create callback after the child association gets
-
# created.
-
after_create save_method
-
after_update save_method
-
else
-
182
define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
-
6
before_save save_method
-
end
-
end
-
-
14
if reflection.validate? && !method_defined?(validation_method)
-
8
method = (collection ? :validate_collection_association : :validate_single_association)
-
2080
define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
-
8
validate validation_method
-
end
-
end
-
end
-
-
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
-
1
def reload(options = nil)
-
4
@marked_for_destruction = false
-
4
@destroyed_by_association = nil
-
4
super
-
end
-
-
# Marks this record to be destroyed as part of the parents save transaction.
-
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
-
# when <tt>parent.save</tt> is called.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
1
def mark_for_destruction
-
@marked_for_destruction = true
-
end
-
-
# Returns whether or not this record will be destroyed as part of the parents save transaction.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
1
def marked_for_destruction?
-
@marked_for_destruction
-
end
-
-
# Records the association that is being destroyed and destroying this
-
# record in the process.
-
1
def destroyed_by_association=(reflection)
-
2
@destroyed_by_association = reflection
-
end
-
-
# Returns the association for the parent being destroyed.
-
#
-
# Used to avoid updating the counter cache unnecessarily.
-
1
def destroyed_by_association
-
@destroyed_by_association
-
end
-
-
# Returns whether or not this record has been changed in any way (including whether
-
# any of its nested autosave associations are likewise changed)
-
1
def changed_for_autosave?
-
new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
-
end
-
-
1
private
-
-
# Returns the record for an association collection that should be validated
-
# or saved. If +autosave+ is +false+ only new records will be returned,
-
# unless the parent is/was a new record itself.
-
1
def associated_records_to_validate_or_save(association, new_record, autosave)
-
if new_record
-
association && association.target
-
elsif autosave
-
association.target.find_all { |record| record.changed_for_autosave? }
-
else
-
association.target.find_all { |record| record.new_record? }
-
end
-
end
-
-
# go through nested autosave associations that are loaded in memory (without loading
-
# any new ones), and return true if is changed for autosave
-
1
def nested_records_changed_for_autosave?
-
self.class.reflect_on_all_autosave_associations.any? do |reflection|
-
association = association_instance_get(reflection.name)
-
association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
-
end
-
end
-
-
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
-
# turned on for the association.
-
1
def validate_single_association(reflection)
-
association = association_instance_get(reflection.name)
-
record = association && association.reader
-
association_valid?(reflection, record) if record
-
end
-
-
# Validate the associated records if <tt>:validate</tt> or
-
# <tt>:autosave</tt> is turned on for the association specified by
-
# +reflection+.
-
1
def validate_collection_association(reflection)
-
2072
if association = association_instance_get(reflection.name)
-
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
-
records.each { |record| association_valid?(reflection, record) }
-
end
-
end
-
end
-
-
# Returns whether or not the association is valid and applies any errors to
-
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
-
# enabled records if they're marked_for_destruction? or destroyed.
-
1
def association_valid?(reflection, record)
-
return true if record.destroyed? || record.marked_for_destruction?
-
-
unless valid = record.valid?
-
if reflection.options[:autosave]
-
record.errors.each do |attribute, message|
-
attribute = "#{reflection.name}.#{attribute}"
-
errors[attribute] << message
-
errors[attribute].uniq!
-
end
-
else
-
errors.add(reflection.name)
-
end
-
end
-
valid
-
end
-
-
# Is used as a before_save callback to check while saving a collection
-
# association whether or not the parent was a new record before saving.
-
1
def before_save_collection_association
-
299
@new_record_before_save = new_record?
-
299
true
-
end
-
-
# Saves any new associated records, or all loaded autosave associations if
-
# <tt>:autosave</tt> is enabled on the association.
-
#
-
# In addition, it destroys all children that were marked for destruction
-
# with mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
1
def save_collection_association(reflection)
-
2392
if association = association_instance_get(reflection.name)
-
autosave = reflection.options[:autosave]
-
-
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
-
-
if autosave
-
records_to_destroy = records.select(&:marked_for_destruction?)
-
records_to_destroy.each { |record| association.destroy(record) }
-
records -= records_to_destroy
-
end
-
-
records.each do |record|
-
next if record.destroyed?
-
-
saved = true
-
-
if autosave != false && (@new_record_before_save || record.new_record?)
-
if autosave
-
saved = association.insert_record(record, false)
-
else
-
association.insert_record(record) unless reflection.nested?
-
end
-
elsif autosave
-
saved = record.save(:validate => false)
-
end
-
-
raise ActiveRecord::Rollback unless saved
-
end
-
end
-
-
# reconstruct the scope now that we know the owner's id
-
association.reset_scope if association.respond_to?(:reset_scope)
-
end
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
-
# on the association.
-
#
-
# In addition, it will destroy the association if it was marked for
-
# destruction with mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
1
def save_has_one_association(reflection)
-
association = association_instance_get(reflection.name)
-
record = association && association.load_target
-
if record && !record.destroyed?
-
autosave = reflection.options[:autosave]
-
-
if autosave && record.marked_for_destruction?
-
record.destroy
-
elsif autosave != false
-
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
-
-
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
-
unless reflection.through_reflection
-
record[reflection.foreign_key] = key
-
end
-
-
saved = record.save(:validate => !autosave)
-
raise ActiveRecord::Rollback if !saved && autosave
-
saved
-
end
-
end
-
end
-
end
-
-
# If the record is new or it has changed, returns true.
-
1
def record_changed?(reflection, record, key)
-
record.new_record? || record[reflection.foreign_key] != key || record.changed_attributes.include?(reflection.foreign_key)
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
-
#
-
# In addition, it will destroy the association if it was marked for destruction.
-
1
def save_belongs_to_association(reflection)
-
176
association = association_instance_get(reflection.name)
-
176
record = association && association.load_target
-
176
if record && !record.destroyed?
-
44
autosave = reflection.options[:autosave]
-
-
44
if autosave && record.marked_for_destruction?
-
self[reflection.foreign_key] = nil
-
record.destroy
-
44
elsif autosave != false
-
44
saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
-
-
44
if association.updated?
-
44
association_id = record.send(reflection.options[:primary_key] || :id)
-
44
self[reflection.foreign_key] = association_id
-
44
association.loaded!
-
end
-
-
44
saved if autosave
-
end
-
end
-
end
-
end
-
end
-
1
require 'yaml'
-
1
require 'set'
-
1
require 'active_support/benchmarkable'
-
1
require 'active_support/dependencies'
-
1
require 'active_support/descendants_tracker'
-
1
require 'active_support/time'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/class/delegating_attributes'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/hash/deep_merge'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/string/behavior'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/introspection'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'active_support/core_ext/class/subclasses'
-
1
require 'arel'
-
1
require 'active_record/errors'
-
1
require 'active_record/log_subscriber'
-
1
require 'active_record/explain_subscriber'
-
-
1
module ActiveRecord #:nodoc:
-
# = Active Record
-
#
-
# Active Record objects don't specify their attributes directly, but rather infer them from
-
# the table definition with which they're linked. Adding, removing, and changing attributes
-
# and their type is done directly in the database. Any change is instantly reflected in the
-
# Active Record objects. The mapping that binds a given Active Record class to a certain
-
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
-
#
-
# See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
-
#
-
# == Creation
-
#
-
# Active Records accept constructor parameters either in a hash or as a block. The hash
-
# method is especially useful when you're receiving the data from somewhere else, like an
-
# HTTP request. It works like this:
-
#
-
# user = User.new(name: "David", occupation: "Code Artist")
-
# user.name # => "David"
-
#
-
# You can also use block initialization:
-
#
-
# user = User.new do |u|
-
# u.name = "David"
-
# u.occupation = "Code Artist"
-
# end
-
#
-
# And of course you can just create a bare object and specify the attributes after the fact:
-
#
-
# user = User.new
-
# user.name = "David"
-
# user.occupation = "Code Artist"
-
#
-
# == Conditions
-
#
-
# Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
-
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
-
# be used for statements that don't involve tainted data. The hash form works much like the array form, except
-
# only equality and range is possible. Examples:
-
#
-
# class User < ActiveRecord::Base
-
# def self.authenticate_unsafely(user_name, password)
-
# where("user_name = '#{user_name}' AND password = '#{password}'").first
-
# end
-
#
-
# def self.authenticate_safely(user_name, password)
-
# where("user_name = ? AND password = ?", user_name, password).first
-
# end
-
#
-
# def self.authenticate_safely_simply(user_name, password)
-
# where(user_name: user_name, password: password).first
-
# end
-
# end
-
#
-
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
-
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
-
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
-
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
-
# before inserting them in the query, which will ensure that an attacker can't escape the
-
# query and fake the login (or worse).
-
#
-
# When using multiple parameters in the conditions, it can easily become hard to read exactly
-
# what the fourth or fifth question mark is supposed to represent. In those cases, you can
-
# resort to named bind variables instead. That's done by replacing the question marks with
-
# symbols and supplying a hash with values for the matching symbol keys:
-
#
-
# Company.where(
-
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
-
# { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' }
-
# ).first
-
#
-
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
-
# operator. For instance:
-
#
-
# Student.where(first_name: "Harvey", status: 1)
-
# Student.where(params[:student])
-
#
-
# A range may be used in the hash to use the SQL BETWEEN operator:
-
#
-
# Student.where(grade: 9..12)
-
#
-
# An array may be used in the hash to use the SQL IN operator:
-
#
-
# Student.where(grade: [9,11,12])
-
#
-
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
-
# can be used to qualify the table name of a particular condition. For instance:
-
#
-
# Student.joins(:schools).where(schools: { category: 'public' })
-
# Student.joins(:schools).where('schools.category' => 'public' )
-
#
-
# == Overwriting default accessors
-
#
-
# All column values are automatically available through basic accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling
-
# <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
-
# change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses an integer of seconds to hold the length of the song
-
#
-
# def length=(minutes)
-
# write_attribute(:length, minutes.to_i * 60)
-
# end
-
#
-
# def length
-
# read_attribute(:length) / 60
-
# end
-
# end
-
#
-
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
-
# instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
-
#
-
# == Attribute query methods
-
#
-
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
-
# Query methods allow you to test whether an attribute value is present.
-
#
-
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
-
# to determine whether the user has a name:
-
#
-
# user = User.new(name: "David")
-
# user.name? # => true
-
#
-
# anonymous = User.new(name: "")
-
# anonymous.name? # => false
-
#
-
# == Accessing attributes before they have been typecasted
-
#
-
# Sometimes you want to be able to read the raw attribute data without having the column-determined
-
# typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
-
# accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
-
# you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
-
#
-
# This is especially useful in validation situations where the user might supply a string for an
-
# integer field and you want to display the original string back in an error message. Accessing the
-
# attribute normally would typecast the string to 0, which isn't what you want.
-
#
-
# == Dynamic attribute-based finders
-
#
-
# Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
-
# by simple queries without turning to SQL. They work by appending the name of an attribute
-
# to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
-
# Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
-
# <tt>Person.find_by_user_name(user_name)</tt>.
-
#
-
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
-
# <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
-
# like <tt>Person.find_by_last_name!</tt>.
-
#
-
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
-
#
-
# Person.find_by(user_name: user_name, password: password)
-
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
-
#
-
# It's even possible to call these dynamic finder methods on relations and named scopes.
-
#
-
# Payment.order("created_on").find_by_amount(50)
-
#
-
# == Saving arrays, hashes, and other non-mappable objects in text columns
-
#
-
# Active Record can serialize any object in text columns using YAML. To do so, you must
-
# specify this with a call to the class method +serialize+.
-
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing
-
# any additional work.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
#
-
# user = User.create(preferences: { "background" => "black", "display" => large })
-
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
-
#
-
# You can also specify a class option as the second parameter that'll raise an exception
-
# if a serialized object is retrieved as a descendant of a class not in the hierarchy.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, Hash
-
# end
-
#
-
# user = User.create(preferences: %w( one two three ))
-
# User.find(user.id).preferences # raises SerializationTypeMismatch
-
#
-
# When you specify a class option, the default value for that attribute will be a new
-
# instance of that class.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, OpenStruct
-
# end
-
#
-
# user = User.new
-
# user.preferences.theme_color = "red"
-
#
-
#
-
# == Single table inheritance
-
#
-
# Active Record allows inheritance by storing the name of the class in a column that by
-
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
-
# This means that an inheritance looking like this:
-
#
-
# class Company < ActiveRecord::Base; end
-
# class Firm < Company; end
-
# class Client < Company; end
-
# class PriorityClient < Client; end
-
#
-
# When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
-
# the companies table with type = "Firm". You can then fetch this row again using
-
# <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
-
#
-
# If you don't have a type column defined in your table, single-table inheritance won't
-
# be triggered. In that case, it'll work just like normal subclasses with no special magic
-
# for differentiating between them or reloading the right type with find.
-
#
-
# Note, all the attributes for all the cases are kept in the same table. Read more:
-
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
-
#
-
# == Connection to multiple databases in different models
-
#
-
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
-
# by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
-
# connection. But you can also set a class-specific connection. For example, if Course is an
-
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
-
# and Course and all of its subclasses will use this connection instead.
-
#
-
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
-
# a Hash indexed by the class. If a connection is requested, the retrieve_connection method
-
# will go up the class-hierarchy until a connection is found in the connection pool.
-
#
-
# == Exceptions
-
#
-
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
-
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
-
# <tt>:adapter</tt> key.
-
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
-
# non-existent adapter
-
# (or a bad spelling of an existing one).
-
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
-
# specified in the association definition.
-
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
-
# <tt>attributes=</tt> method.
-
# You can inspect the +attribute+ property of the exception object to determine which attribute
-
# triggered the error.
-
# * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
-
# before querying.
-
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
-
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
-
# AttributeAssignmentError
-
# objects that should be inspected to determine which attributes triggered the errors.
-
# * RecordInvalid - raised by save! and create! when the record is invalid.
-
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
-
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
-
# nothing was found, please check its documentation for further details.
-
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
-
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
-
#
-
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
-
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
-
# instances in the current object space.
-
1
class Base
-
1
extend ActiveModel::Naming
-
-
1
extend ActiveSupport::Benchmarkable
-
1
extend ActiveSupport::DescendantsTracker
-
-
1
extend ConnectionHandling
-
1
extend QueryCache::ClassMethods
-
1
extend Querying
-
1
extend Translation
-
1
extend DynamicMatchers
-
1
extend Explain
-
-
1
include Persistence
-
1
include ReadonlyAttributes
-
1
include ModelSchema
-
1
include Inheritance
-
1
include Scoping
-
1
include Sanitization
-
1
include AttributeAssignment
-
1
include ActiveModel::Conversion
-
1
include Integration
-
1
include Validations
-
1
include CounterCache
-
1
include Locking::Optimistic
-
1
include Locking::Pessimistic
-
1
include AttributeMethods
-
1
include Callbacks
-
1
include Timestamp
-
1
include Associations
-
1
include ActiveModel::SecurePassword
-
1
include AutosaveAssociation
-
1
include NestedAttributes
-
1
include Aggregations
-
1
include Transactions
-
1
include Reflection
-
1
include Serialization
-
1
include Store
-
1
include Core
-
end
-
-
1
ActiveSupport.run_load_hooks(:active_record, Base)
-
end
-
1
module ActiveRecord
-
# = Active Record Callbacks
-
#
-
# Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
-
# before or after an alteration of the object state. This can be used to make sure that associated and
-
# dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
-
# before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
-
# the <tt>Base#save</tt> call for a new record:
-
#
-
# * (-) <tt>save</tt>
-
# * (-) <tt>valid</tt>
-
# * (1) <tt>before_validation</tt>
-
# * (-) <tt>validate</tt>
-
# * (2) <tt>after_validation</tt>
-
# * (3) <tt>before_save</tt>
-
# * (4) <tt>before_create</tt>
-
# * (-) <tt>create</tt>
-
# * (5) <tt>after_create</tt>
-
# * (6) <tt>after_save</tt>
-
# * (7) <tt>after_commit</tt>
-
#
-
# Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
-
# Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
-
# <tt>after_rollback</tt>.
-
#
-
# Additionally, an <tt>after_touch</tt> callback is triggered whenever an
-
# object is touched.
-
#
-
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
-
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
-
# are instantiated as well.
-
#
-
# There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
-
# Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
-
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
-
#
-
# Examples:
-
# class CreditCard < ActiveRecord::Base
-
# # Strip everything but digits, so the user can specify "555 234 34" or
-
# # "5552-3434" and both will mean "55523434"
-
# before_validation(on: :create) do
-
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
-
# end
-
# end
-
#
-
# class Subscription < ActiveRecord::Base
-
# before_create :record_signup
-
#
-
# private
-
# def record_signup
-
# self.signed_up_on = Date.today
-
# end
-
# end
-
#
-
# class Firm < ActiveRecord::Base
-
# # Destroys the associated clients and people when the firm is destroyed
-
# before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
-
# before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
-
# end
-
#
-
# == Inheritable callback queues
-
#
-
# Besides the overwritable callback methods, it's also possible to register callbacks through the
-
# use of the callback macros. Their main advantage is that the macros add behavior into a callback
-
# queue that is kept intact down through an inheritance hierarchy.
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :destroy_author
-
# end
-
#
-
# class Reply < Topic
-
# before_destroy :destroy_readers
-
# end
-
#
-
# Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
-
# run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
-
# where the +before_destroy+ method is overridden:
-
#
-
# class Topic < ActiveRecord::Base
-
# def before_destroy() destroy_author end
-
# end
-
#
-
# class Reply < Topic
-
# def before_destroy() destroy_readers end
-
# end
-
#
-
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
-
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
-
# hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
-
# to decide whether they want to call +super+ and trigger the inherited callbacks.
-
#
-
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
-
# callbacks before specifying the associations. Otherwise, you might trigger the loading of a
-
# child before the parent has registered the callbacks and they won't be inherited.
-
#
-
# == Types of callbacks
-
#
-
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
-
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
-
# are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
-
# creating mix-ins), and inline eval methods are deprecated.
-
#
-
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :delete_parents
-
#
-
# private
-
# def delete_parents
-
# self.class.delete_all "parent_id = #{id}"
-
# end
-
# end
-
#
-
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new
-
# after_save EncryptionWrapper.new
-
# after_initialize EncryptionWrapper.new
-
# end
-
#
-
# class EncryptionWrapper
-
# def before_save(record)
-
# record.credit_card_number = encrypt(record.credit_card_number)
-
# end
-
#
-
# def after_save(record)
-
# record.credit_card_number = decrypt(record.credit_card_number)
-
# end
-
#
-
# alias_method :after_initialize, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
-
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
-
# initialization data such as the name of the attribute to work with:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new("credit_card_number")
-
# after_save EncryptionWrapper.new("credit_card_number")
-
# after_initialize EncryptionWrapper.new("credit_card_number")
-
# end
-
#
-
# class EncryptionWrapper
-
# def initialize(attribute)
-
# @attribute = attribute
-
# end
-
#
-
# def before_save(record)
-
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# def after_save(record)
-
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# alias_method :after_initialize, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also
-
# pass a "method string", which will then be evaluated within the binding of the callback. Example:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
-
# end
-
#
-
# Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
-
# is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
-
# 'puts "Evaluated after parents are destroyed"'
-
# end
-
#
-
# == <tt>before_validation*</tt> returning statements
-
#
-
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
-
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
-
# ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
-
#
-
# == Canceling callbacks
-
#
-
# If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
-
# cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
-
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
-
# methods on the model, which are called last.
-
#
-
# == Ordering callbacks
-
#
-
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
-
# callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
-
#
-
# Let's look at the code below:
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: destroy
-
#
-
# before_destroy :log_children
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
-
# because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: destroy
-
#
-
# before_destroy :log_children, prepend: true
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
-
#
-
# == Transactions
-
#
-
# The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
-
# within a transaction. That includes <tt>after_*</tt> hooks. If everything
-
# goes fine a COMMIT is executed once the chain has been completed.
-
#
-
# If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
-
# can also trigger a ROLLBACK raising an exception in any of the callbacks,
-
# including <tt>after_*</tt> hooks. Note, however, that in that case the client
-
# needs to be aware of it because an ordinary +save+ will raise such exception
-
# instead of quietly returning +false+.
-
#
-
# == Debugging callbacks
-
#
-
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
-
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
-
# defines what part of the chain the callback runs in.
-
#
-
# To find all callbacks in the before_save callback chain:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
-
#
-
# Returns an array of callback objects that form the before_save chain.
-
#
-
# To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
-
#
-
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
-
#
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
-
1
CALLBACKS = [
-
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
-
:before_save, :around_save, :after_save, :before_create, :around_create,
-
:after_create, :before_update, :around_update, :after_update,
-
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
-
]
-
-
1
module ClassMethods
-
1
include ActiveModel::Callbacks
-
end
-
-
1
included do
-
1
include ActiveModel::Validations::Callbacks
-
-
1
define_model_callbacks :initialize, :find, :touch, :only => :after
-
1
define_model_callbacks :save, :create, :update, :destroy
-
end
-
-
1
def destroy #:nodoc:
-
24
run_callbacks(:destroy) { super }
-
end
-
-
1
def touch(*) #:nodoc:
-
run_callbacks(:touch) { super }
-
end
-
-
1
private
-
-
1
def create_or_update #:nodoc:
-
774
run_callbacks(:save) { super }
-
end
-
-
1
def create_record #:nodoc:
-
582
run_callbacks(:create) { super }
-
end
-
-
1
def update_record(*) #:nodoc:
-
192
run_callbacks(:update) { super }
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class Transaction #:nodoc:
-
1
attr_reader :connection
-
-
1
def initialize(connection)
-
586
@connection = connection
-
586
@state = TransactionState.new
-
end
-
-
1
def state
-
367
@state
-
end
-
end
-
-
1
class TransactionState
-
1
attr_accessor :parent
-
-
1
VALID_STATES = Set.new([:committed, :rolledback, nil])
-
-
1
def initialize(state = nil)
-
586
@state = state
-
586
@parent = nil
-
end
-
-
1
def finalized?
-
200
@state
-
end
-
-
1
def committed?
-
@state == :committed
-
end
-
-
1
def rolledback?
-
@state == :rolledback
-
end
-
-
1
def set_state(state)
-
585
if !VALID_STATES.include?(state)
-
raise ArgumentError, "Invalid transaction state: #{state}"
-
end
-
585
@state = state
-
end
-
end
-
-
1
class ClosedTransaction < Transaction #:nodoc:
-
1
def number
-
758
0
-
end
-
-
1
def begin(options = {})
-
206
RealTransaction.new(connection, self, options)
-
end
-
-
1
def closed?
-
true
-
end
-
-
1
def open?
-
false
-
end
-
-
1
def joinable?
-
30
false
-
end
-
-
# This is a noop when there are no open transactions
-
1
def add_record(record)
-
end
-
end
-
-
1
class OpenTransaction < Transaction #:nodoc:
-
1
attr_reader :parent, :records
-
1
attr_writer :joinable
-
-
1
def initialize(connection, parent, options = {})
-
585
super connection
-
-
585
@parent = parent
-
585
@records = []
-
585
@finishing = false
-
585
@joinable = options.fetch(:joinable, true)
-
end
-
-
# This state is necessary so that we correctly handle stuff that might
-
# happen in a commit/rollback. But it's kinda distasteful. Maybe we can
-
# find a better way to structure it in the future.
-
1
def finishing?
-
1564
@finishing
-
end
-
-
1
def joinable?
-
427
@joinable && !finishing?
-
end
-
-
1
def number
-
1137
if finishing?
-
379
parent.number
-
else
-
758
parent.number + 1
-
end
-
end
-
-
1
def begin(options = {})
-
379
if finishing?
-
parent.begin
-
else
-
379
SavepointTransaction.new(connection, self, options)
-
end
-
end
-
-
1
def rollback
-
188
@finishing = true
-
188
perform_rollback
-
188
parent
-
end
-
-
1
def commit
-
397
@finishing = true
-
397
perform_commit
-
397
parent
-
end
-
-
1
def add_record(record)
-
417
if record.has_transactional_callbacks?
-
315
records << record
-
else
-
102
record.set_transaction_state(@state)
-
end
-
end
-
-
1
def rollback_records
-
188
@state.set_state(:rolledback)
-
188
records.uniq.each do |record|
-
8
begin
-
8
record.rolledback!(parent.closed?)
-
rescue => e
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
end
-
-
1
def commit_records
-
30
@state.set_state(:committed)
-
30
records.uniq.each do |record|
-
30
begin
-
30
record.committed!
-
rescue => e
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
end
-
-
1
def closed?
-
8
false
-
end
-
-
1
def open?
-
176
true
-
end
-
end
-
-
1
class RealTransaction < OpenTransaction #:nodoc:
-
1
def initialize(connection, parent, options = {})
-
206
super
-
-
206
if options[:isolation]
-
connection.begin_isolated_db_transaction(options[:isolation])
-
else
-
206
connection.begin_db_transaction
-
end
-
end
-
-
1
def perform_rollback
-
176
connection.rollback_db_transaction
-
176
rollback_records
-
end
-
-
1
def perform_commit
-
30
connection.commit_db_transaction
-
30
commit_records
-
end
-
end
-
-
1
class SavepointTransaction < OpenTransaction #:nodoc:
-
1
def initialize(connection, parent, options = {})
-
379
if options[:isolation]
-
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
-
end
-
-
379
super
-
379
connection.create_savepoint
-
end
-
-
1
def perform_rollback
-
12
connection.rollback_to_savepoint
-
12
rollback_records
-
end
-
-
1
def perform_commit
-
367
@state.set_state(:committed)
-
367
@state.parent = parent.state
-
367
connection.release_savepoint
-
end
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActiveRecord
-
# :stopdoc:
-
1
module ConnectionAdapters
-
# An abstract definition of a column in a table.
-
1
class Column
-
1
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
-
1
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
-
-
1
module Format
-
1
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
-
1
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
-
end
-
-
1
attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function
-
1
attr_accessor :primary, :coder
-
-
1
alias :encoded? :coder
-
-
# Instantiates a new column in the table.
-
#
-
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
-
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
-
# +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
-
# <tt>company_name varchar(60)</tt>.
-
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
-
# +null+ determines if this column allows +NULL+ values.
-
1
def initialize(name, default, sql_type = nil, null = true)
-
27
@name = name
-
27
@sql_type = sql_type
-
27
@null = null
-
27
@limit = extract_limit(sql_type)
-
27
@precision = extract_precision(sql_type)
-
27
@scale = extract_scale(sql_type)
-
27
@type = simplified_type(sql_type)
-
27
@default = extract_default(default)
-
27
@default_function = nil
-
27
@primary = nil
-
27
@coder = nil
-
end
-
-
# Returns +true+ if the column is either of type string or text.
-
1
def text?
-
1036
type == :string || type == :text
-
end
-
-
# Returns +true+ if the column is either of type integer, float or decimal.
-
1
def number?
-
9417
type == :integer || type == :float || type == :decimal
-
end
-
-
1
def has_default?
-
!default.nil?
-
end
-
-
# Returns the Ruby class that corresponds to the abstract data type.
-
1
def klass
-
case type
-
when :integer then Fixnum
-
when :float then Float
-
when :decimal then BigDecimal
-
when :datetime, :timestamp, :time then Time
-
when :date then Date
-
when :text, :string, :binary then String
-
when :boolean then Object
-
end
-
end
-
-
1
def binary?
-
5497
type == :binary
-
end
-
-
# Casts a Ruby value to something appropriate for writing to the database.
-
1
def type_cast_for_write(value)
-
2981
return value unless number?
-
-
659
case value
-
when FalseClass
-
0
-
when TrueClass
-
1
-
when String
-
13
value.presence
-
else
-
646
value
-
end
-
end
-
-
# Casts value (which is a String) to an appropriate instance.
-
1
def type_cast(value)
-
13708
return nil if value.nil?
-
7530
return coder.load(value) if encoded?
-
-
7530
klass = self.class
-
-
7530
case type
-
4315
when :string, :text then value
-
1700
when :integer then klass.value_to_integer(value)
-
when :float then value.to_f
-
when :decimal then klass.value_to_decimal(value)
-
1498
when :datetime, :timestamp then klass.string_to_time(value)
-
when :time then klass.string_to_dummy_time(value)
-
when :date then klass.value_to_date(value)
-
when :binary then klass.binary_to_string(value)
-
17
when :boolean then klass.value_to_boolean(value)
-
else value
-
end
-
end
-
-
1
def type_cast_code(var_name)
-
message = "Column#type_cast_code is deprecated in favor of using Column#type_cast only, " \
-
"and it is going to be removed in future Rails versions."
-
ActiveSupport::Deprecation.warn message
-
-
klass = self.class.name
-
-
case type
-
when :string, :text then var_name
-
when :integer then "#{klass}.value_to_integer(#{var_name})"
-
when :float then "#{var_name}.to_f"
-
when :decimal then "#{klass}.value_to_decimal(#{var_name})"
-
when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
-
when :time then "#{klass}.string_to_dummy_time(#{var_name})"
-
when :date then "#{klass}.value_to_date(#{var_name})"
-
when :binary then "#{klass}.binary_to_string(#{var_name})"
-
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
-
when :hstore then "#{klass}.string_to_hstore(#{var_name})"
-
when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
-
when :json then "#{klass}.string_to_json(#{var_name})"
-
else var_name
-
end
-
end
-
-
# Returns the human name of the column name.
-
#
-
# ===== Examples
-
# Column.new('sales_stage', ...).human_name # => 'Sales stage'
-
1
def human_name
-
Base.human_attribute_name(@name)
-
end
-
-
1
def extract_default(default)
-
27
type_cast(default)
-
end
-
-
# Used to convert from Strings to BLOBs
-
1
def string_to_binary(value)
-
self.class.string_to_binary(value)
-
end
-
-
1
class << self
-
# Used to convert from Strings to BLOBs
-
1
def string_to_binary(value)
-
value
-
end
-
-
# Used to convert from BLOBs to Strings
-
1
def binary_to_string(value)
-
value
-
end
-
-
1
def value_to_date(value)
-
if value.is_a?(String)
-
return nil if value.empty?
-
fast_string_to_date(value) || fallback_string_to_date(value)
-
elsif value.respond_to?(:to_date)
-
value.to_date
-
else
-
value
-
end
-
end
-
-
1
def string_to_time(string)
-
1498
return string unless string.is_a?(String)
-
190
return nil if string.empty?
-
-
190
fast_string_to_time(string) || fallback_string_to_time(string)
-
end
-
-
1
def string_to_dummy_time(string)
-
return string unless string.is_a?(String)
-
return nil if string.empty?
-
-
dummy_time_string = "2000-01-01 #{string}"
-
-
fast_string_to_time(dummy_time_string) || begin
-
time_hash = Date._parse(dummy_time_string)
-
return nil if time_hash[:hour].nil?
-
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
-
end
-
end
-
-
# convert something to a boolean
-
1
def value_to_boolean(value)
-
17
if value.is_a?(String) && value.empty?
-
nil
-
else
-
17
TRUE_VALUES.include?(value)
-
end
-
end
-
-
# Used to convert values to integer.
-
# handle the case when an integer column is used to store boolean values
-
1
def value_to_integer(value)
-
1700
case value
-
when TrueClass, FalseClass
-
value ? 1 : 0
-
else
-
1700
value.to_i rescue nil
-
end
-
end
-
-
# convert something to a BigDecimal
-
1
def value_to_decimal(value)
-
# Using .class is faster than .is_a? and
-
# subclasses of BigDecimal will be handled
-
# in the else clause
-
if value.class == BigDecimal
-
value
-
elsif value.respond_to?(:to_d)
-
value.to_d
-
else
-
value.to_s.to_d
-
end
-
end
-
-
1
protected
-
# '0.123456' -> 123456
-
# '1.123456' -> 123456
-
1
def microseconds(time)
-
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
-
end
-
-
1
def new_date(year, mon, mday)
-
if year && year != 0
-
Date.new(year, mon, mday) rescue nil
-
end
-
end
-
-
1
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
-
# Treat 0000-00-00 00:00:00 as nil.
-
190
return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
-
-
190
if offset
-
time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
-
return nil unless time
-
-
time -= offset
-
Base.default_timezone == :utc ? time : time.getlocal
-
else
-
190
Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
-
end
-
end
-
-
1
def fast_string_to_date(string)
-
if string =~ Format::ISO_DATE
-
new_date $1.to_i, $2.to_i, $3.to_i
-
end
-
end
-
-
# Doesn't handle time zones.
-
1
def fast_string_to_time(string)
-
190
if string =~ Format::ISO_DATETIME
-
190
microsec = ($7.to_r * 1_000_000).to_i
-
190
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
-
end
-
end
-
-
1
def fallback_string_to_date(string)
-
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
-
end
-
-
1
def fallback_string_to_time(string)
-
time_hash = Date._parse(string)
-
time_hash[:sec_fraction] = microseconds(time_hash)
-
-
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
-
end
-
end
-
-
1
private
-
1
def extract_limit(sql_type)
-
27
$1.to_i if sql_type =~ /\((.*)\)/
-
end
-
-
1
def extract_precision(sql_type)
-
27
$2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
-
end
-
-
1
def extract_scale(sql_type)
-
27
case sql_type
-
when /^(numeric|decimal|number)\((\d+)\)/i then 0
-
when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
-
end
-
end
-
-
1
def simplified_type(field_type)
-
27
case field_type
-
when /int/i
-
10
:integer
-
when /float|double/i
-
:float
-
when /decimal|numeric|number/i
-
extract_scale(field_type) == 0 ? :integer : :decimal
-
when /datetime/i
-
8
:datetime
-
when /timestamp/i
-
:timestamp
-
when /time/i
-
:time
-
when /date/i
-
:date
-
when /clob/i, /text/i
-
:text
-
when /blob/i, /binary/i
-
:binary
-
when /char/i, /string/i
-
8
:string
-
when /boolean/i
-
1
:boolean
-
end
-
end
-
end
-
end
-
# :startdoc:
-
end
-
1
require 'uri'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class ConnectionSpecification #:nodoc:
-
1
attr_reader :config, :adapter_method
-
-
1
def initialize(config, adapter_method)
-
1
@config, @adapter_method = config, adapter_method
-
end
-
-
1
def initialize_dup(original)
-
@config = original.config.dup
-
end
-
-
##
-
# Builds a ConnectionSpecification from user input
-
1
class Resolver # :nodoc:
-
1
attr_reader :config, :klass, :configurations
-
-
1
def initialize(config, configurations)
-
1
@config = config
-
1
@configurations = configurations
-
end
-
-
1
def spec
-
1
case config
-
when nil
-
1
raise AdapterNotSpecified unless defined?(Rails.env)
-
1
resolve_string_connection Rails.env
-
when Symbol, String
-
resolve_string_connection config.to_s
-
when Hash
-
resolve_hash_connection config
-
end
-
end
-
-
1
private
-
1
def resolve_string_connection(spec) # :nodoc:
-
1
hash = configurations.fetch(spec) do |k|
-
connection_url_to_hash(k)
-
end
-
-
1
raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
-
-
1
resolve_hash_connection hash
-
end
-
-
1
def resolve_hash_connection(spec) # :nodoc:
-
1
spec = spec.symbolize_keys
-
-
1
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
-
-
1
path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
-
1
begin
-
1
require path_to_adapter
-
rescue Gem::LoadError => e
-
raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile."
-
rescue LoadError => e
-
raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
-
end
-
-
1
adapter_method = "#{spec[:adapter]}_connection"
-
-
1
ConnectionSpecification.new(spec, adapter_method)
-
end
-
-
1
def connection_url_to_hash(url) # :nodoc:
-
config = URI.parse url
-
adapter = config.scheme
-
adapter = "postgresql" if adapter == "postgres"
-
spec = { :adapter => adapter,
-
:username => config.user,
-
:password => config.password,
-
:port => config.port,
-
:database => config.path.sub(%r{^/},""),
-
:host => config.host }
-
-
spec.reject!{ |_,value| value.blank? }
-
-
uri_parser = URI::Parser.new
-
-
spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
-
-
if config.query
-
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
-
-
spec.merge!(options)
-
end
-
-
spec
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_record/connection_adapters/abstract_adapter'
-
1
require 'active_record/connection_adapters/statement_pool'
-
1
require 'arel/visitors/bind_visitor'
-
-
1
gem 'sqlite3', '~> 1.3.6'
-
1
require 'sqlite3'
-
-
1
module ActiveRecord
-
1
module ConnectionHandling # :nodoc:
-
# sqlite3 adapter reuses sqlite_connection.
-
1
def sqlite3_connection(config)
-
# Require database.
-
1
unless config[:database]
-
raise ArgumentError, "No database file specified. Missing argument: database"
-
end
-
-
# Allow database path relative to Rails.root, but only if
-
# the database path is not the special path that tells
-
# Sqlite to build a database only in memory.
-
1
if ':memory:' != config[:database]
-
1
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
-
1
dirname = File.dirname(config[:database])
-
1
Dir.mkdir(dirname) unless File.directory?(dirname)
-
end
-
-
1
db = SQLite3::Database.new(
-
config[:database].to_s,
-
:results_as_hash => true
-
)
-
-
1
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
-
-
1
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
-
end
-
end
-
-
1
module ConnectionAdapters #:nodoc:
-
1
class SQLite3Column < Column #:nodoc:
-
1
class << self
-
1
def binary_to_string(value)
-
if value.encoding != Encoding::ASCII_8BIT
-
value = value.force_encoding(Encoding::ASCII_8BIT)
-
end
-
value
-
end
-
end
-
end
-
-
# The SQLite3 adapter works SQLite 3.6.16 or newer
-
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
-
#
-
# Options:
-
#
-
# * <tt>:database</tt> - Path to the database file.
-
1
class SQLite3Adapter < AbstractAdapter
-
1
class Version
-
1
include Comparable
-
-
1
def initialize(version_string)
-
@version = version_string.split('.').map { |v| v.to_i }
-
end
-
-
1
def <=>(version_string)
-
@version <=> version_string.split('.').map { |v| v.to_i }
-
end
-
end
-
-
1
class StatementPool < ConnectionAdapters::StatementPool
-
1
def initialize(connection, max)
-
1
super
-
2
@cache = Hash.new { |h,pid| h[pid] = {} }
-
end
-
-
1
def each(&block); cache.each(&block); end
-
1
def key?(key); cache.key?(key); end
-
905
def [](key); cache[key]; end
-
1
def length; cache.length; end
-
-
1
def []=(sql, key)
-
36
while @max <= cache.size
-
dealloc(cache.shift.last[:stmt])
-
end
-
36
cache[sql] = key
-
end
-
-
1
def clear
-
cache.values.each do |hash|
-
dealloc hash[:stmt]
-
end
-
cache.clear
-
end
-
-
1
private
-
1
def cache
-
976
@cache[$$]
-
end
-
-
1
def dealloc(stmt)
-
stmt.close unless stmt.closed?
-
end
-
end
-
-
1
class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
-
1
include Arel::Visitors::BindVisitor
-
end
-
-
1
def initialize(connection, logger, config)
-
1
super(connection, logger)
-
-
1
@active = nil
-
1
@statements = StatementPool.new(@connection,
-
1
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
-
1
@config = config
-
-
2
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
-
1
@prepared_statements = true
-
1
@visitor = Arel::Visitors::SQLite.new self
-
else
-
@visitor = unprepared_visitor
-
end
-
end
-
-
1
def adapter_name #:nodoc:
-
'SQLite'
-
end
-
-
# Returns true
-
1
def supports_ddl_transactions?
-
true
-
end
-
-
# Returns true if SQLite version is '3.6.8' or greater, false otherwise.
-
1
def supports_savepoints?
-
sqlite_version >= '3.6.8'
-
end
-
-
# Returns true, since this connection adapter supports prepared statement
-
# caching.
-
1
def supports_statement_cache?
-
true
-
end
-
-
# Returns true, since this connection adapter supports migrations.
-
1
def supports_migrations? #:nodoc:
-
true
-
end
-
-
# Returns true.
-
1
def supports_primary_key? #:nodoc:
-
true
-
end
-
-
1
def requires_reloading?
-
true
-
end
-
-
# Returns true
-
1
def supports_add_column?
-
true
-
end
-
-
1
def active?
-
176
@active != false
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
1
def disconnect!
-
super
-
@active = false
-
@connection.close rescue nil
-
end
-
-
# Clears the prepared statements cache.
-
1
def clear_cache!
-
@statements.clear
-
end
-
-
# Returns true
-
1
def supports_count_distinct? #:nodoc:
-
true
-
end
-
-
# Returns true
-
1
def supports_autoincrement? #:nodoc:
-
true
-
end
-
-
1
def supports_index_sort_order?
-
true
-
end
-
-
# Returns 62. SQLite supports index names up to 64
-
# characters. The rest is used by rails internally to perform
-
# temporary rename operations
-
1
def allowed_index_name_length
-
index_name_length - 2
-
end
-
-
1
def native_database_types #:nodoc:
-
{
-
:primary_key => default_primary_key_type,
-
:string => { :name => "varchar", :limit => 255 },
-
:text => { :name => "text" },
-
:integer => { :name => "integer" },
-
:float => { :name => "float" },
-
:decimal => { :name => "decimal" },
-
:datetime => { :name => "datetime" },
-
:timestamp => { :name => "datetime" },
-
:time => { :name => "time" },
-
:date => { :name => "date" },
-
:binary => { :name => "blob" },
-
:boolean => { :name => "boolean" }
-
}
-
end
-
-
# Returns the current database encoding format as a string, eg: 'UTF-8'
-
1
def encoding
-
@connection.encoding.to_s
-
end
-
-
# Returns true.
-
1
def supports_explain?
-
true
-
end
-
-
# QUOTING ==================================================
-
-
1
def quote(value, column = nil)
-
1185
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
-
s = column.class.string_to_binary(value).unpack("H*")[0]
-
"x'#{s}'"
-
else
-
1185
super
-
end
-
end
-
-
1
def quote_string(s) #:nodoc:
-
945
@connection.class.quote(s)
-
end
-
-
1
def quote_table_name_for_assignment(table, attr)
-
quote_column_name(attr)
-
end
-
-
1
def quote_column_name(name) #:nodoc:
-
1789
%Q("#{name.to_s.gsub('"', '""')}")
-
end
-
-
# Quote date/time values for use in SQL input. Includes microseconds
-
# if the value is a Time responding to usec.
-
1
def quoted_date(value) #:nodoc:
-
656
if value.respond_to?(:usec)
-
656
"#{super}.#{sprintf("%06d", value.usec)}"
-
else
-
super
-
end
-
end
-
-
1
def type_cast(value, column) # :nodoc:
-
2996
return value.to_f if BigDecimal === value
-
2996
return super unless String === value
-
1747
return super unless column && value
-
-
1747
value = super
-
1747
if column.type == :string && value.encoding == Encoding::ASCII_8BIT
-
208
logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
-
208
value = value.encode Encoding::UTF_8
-
end
-
1747
value
-
end
-
-
# DATABASE STATEMENTS ======================================
-
-
1
def explain(arel, binds = [])
-
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
-
end
-
-
1
class ExplainPrettyPrinter
-
# Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
-
# the output of the SQLite shell:
-
#
-
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
-
# 0|1|1|SCAN TABLE posts (~100000 rows)
-
#
-
1
def pp(result) # :nodoc:
-
result.rows.map do |row|
-
row.join('|')
-
end.join("\n") + "\n"
-
end
-
end
-
-
1
def exec_query(sql, name = nil, binds = [])
-
1956
log(sql, name, binds) do
-
-
# Don't cache statements if they are not prepared
-
1956
if without_prepared_statement?(binds)
-
1052
stmt = @connection.prepare(sql)
-
1052
cols = stmt.columns
-
1052
records = stmt.to_a
-
1052
stmt.close
-
1052
stmt = records
-
else
-
904
cache = @statements[sql] ||= {
-
:stmt => @connection.prepare(sql)
-
}
-
904
stmt = cache[:stmt]
-
904
cols = cache[:cols] ||= stmt.columns
-
904
stmt.reset!
-
904
stmt.bind_params binds.map { |col, val|
-
2478
type_cast(val, col)
-
}
-
end
-
-
1956
ActiveRecord::Result.new(cols, stmt.to_a)
-
end
-
end
-
-
1
def exec_delete(sql, name = 'SQL', binds = [])
-
87
exec_query(sql, name, binds)
-
87
@connection.changes
-
end
-
1
alias :exec_update :exec_delete
-
-
1
def last_inserted_id(result)
-
291
@connection.last_insert_row_id
-
end
-
-
1
def execute(sql, name = nil) #:nodoc:
-
1516
log(sql, name) { @connection.execute(sql) }
-
end
-
-
1
def update_sql(sql, name = nil) #:nodoc:
-
super
-
@connection.changes
-
end
-
-
1
def delete_sql(sql, name = nil) #:nodoc:
-
sql += " WHERE 1=1" unless sql =~ /WHERE/i
-
super sql, name
-
end
-
-
1
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
-
super
-
id_value || @connection.last_insert_row_id
-
end
-
1
alias :create :insert_sql
-
-
1
def select_rows(sql, name = nil, binds = [])
-
exec_query(sql, name, binds).rows
-
end
-
-
1
def create_savepoint
-
379
execute("SAVEPOINT #{current_savepoint_name}")
-
end
-
-
1
def rollback_to_savepoint
-
12
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
-
end
-
-
1
def release_savepoint
-
367
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
-
end
-
-
1
def begin_db_transaction #:nodoc:
-
412
log('begin transaction',nil) { @connection.transaction }
-
end
-
-
1
def commit_db_transaction #:nodoc:
-
60
log('commit transaction',nil) { @connection.commit }
-
end
-
-
1
def rollback_db_transaction #:nodoc:
-
352
log('rollback transaction',nil) { @connection.rollback }
-
end
-
-
# SCHEMA STATEMENTS ========================================
-
-
1
def tables(name = nil, table_name = nil) #:nodoc:
-
6
sql = <<-SQL
-
SELECT name
-
FROM sqlite_master
-
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
-
SQL
-
6
sql << " AND name = #{quote_table_name(table_name)}" if table_name
-
-
6
exec_query(sql, 'SCHEMA').map do |row|
-
6
row['name']
-
end
-
end
-
-
1
def table_exists?(table_name)
-
6
table_name && tables(nil, table_name).any?
-
end
-
-
# Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
-
1
def columns(table_name) #:nodoc:
-
5
table_structure(table_name).map do |field|
-
27
case field["dflt_value"]
-
when /^null$/i
-
field["dflt_value"] = nil
-
when /^'(.*)'$/m
-
field["dflt_value"] = $1.gsub("''", "'")
-
when /^"(.*)"$/m
-
field["dflt_value"] = $1.gsub('""', '"')
-
end
-
-
27
SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
-
end
-
end
-
-
# Returns an array of indexes for the given table.
-
1
def indexes(table_name, name = nil) #:nodoc:
-
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
-
IndexDefinition.new(
-
table_name,
-
row['name'],
-
row['unique'] != 0,
-
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
-
col['name']
-
})
-
end
-
end
-
-
1
def primary_key(table_name) #:nodoc:
-
5
column = table_structure(table_name).find { |field|
-
5
field['pk'] == 1
-
}
-
5
column && column['name']
-
end
-
-
1
def remove_index!(table_name, index_name) #:nodoc:
-
exec_query "DROP INDEX #{quote_column_name(index_name)}"
-
end
-
-
# Renames a table.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
1
def rename_table(table_name, new_name)
-
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
-
rename_table_indexes(table_name, new_name)
-
end
-
-
# See: http://www.sqlite.org/lang_altertable.html
-
# SQLite has an additional restriction on the ALTER TABLE statement
-
1
def valid_alter_table_options( type, options)
-
type.to_sym != :primary_key
-
end
-
-
1
def add_column(table_name, column_name, type, options = {}) #:nodoc:
-
if supports_add_column? && valid_alter_table_options( type, options )
-
super(table_name, column_name, type, options)
-
else
-
alter_table(table_name) do |definition|
-
definition.column(column_name, type, options)
-
end
-
end
-
end
-
-
1
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
-
alter_table(table_name) do |definition|
-
definition.remove_column column_name
-
end
-
end
-
-
1
def change_column_default(table_name, column_name, default) #:nodoc:
-
alter_table(table_name) do |definition|
-
definition[column_name].default = default
-
end
-
end
-
-
1
def change_column_null(table_name, column_name, null, default = nil)
-
unless null || default.nil?
-
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
-
end
-
alter_table(table_name) do |definition|
-
definition[column_name].null = null
-
end
-
end
-
-
1
def change_column(table_name, column_name, type, options = {}) #:nodoc:
-
alter_table(table_name) do |definition|
-
include_default = options_include_default?(options)
-
definition[column_name].instance_eval do
-
self.type = type
-
self.limit = options[:limit] if options.include?(:limit)
-
self.default = options[:default] if include_default
-
self.null = options[:null] if options.include?(:null)
-
self.precision = options[:precision] if options.include?(:precision)
-
self.scale = options[:scale] if options.include?(:scale)
-
end
-
end
-
end
-
-
1
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
unless columns(table_name).detect{|c| c.name == column_name.to_s }
-
raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
-
end
-
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
-
rename_column_indexes(table_name, column_name, new_column_name)
-
end
-
-
1
protected
-
1
def select(sql, name = nil, binds = []) #:nodoc:
-
1562
exec_query(sql, name, binds)
-
end
-
-
1
def table_structure(table_name)
-
10
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
-
10
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
-
10
structure
-
end
-
-
1
def alter_table(table_name, options = {}) #:nodoc:
-
altered_table_name = "a#{table_name}"
-
caller = lambda {|definition| yield definition if block_given?}
-
-
transaction do
-
move_table(table_name, altered_table_name,
-
options.merge(:temporary => true))
-
move_table(altered_table_name, table_name, &caller)
-
end
-
end
-
-
1
def move_table(from, to, options = {}, &block) #:nodoc:
-
copy_table(from, to, options, &block)
-
drop_table(from)
-
end
-
-
1
def copy_table(from, to, options = {}) #:nodoc:
-
from_primary_key = primary_key(from)
-
options[:id] = false
-
create_table(to, options) do |definition|
-
@definition = definition
-
@definition.primary_key(from_primary_key) if from_primary_key.present?
-
columns(from).each do |column|
-
column_name = options[:rename] ?
-
(options[:rename][column.name] ||
-
options[:rename][column.name.to_sym] ||
-
column.name) : column.name
-
next if column_name == from_primary_key
-
-
@definition.column(column_name, column.type,
-
:limit => column.limit, :default => column.default,
-
:precision => column.precision, :scale => column.scale,
-
:null => column.null)
-
end
-
yield @definition if block_given?
-
end
-
copy_table_indexes(from, to, options[:rename] || {})
-
copy_table_contents(from, to,
-
@definition.columns.map {|column| column.name},
-
options[:rename] || {})
-
end
-
-
1
def copy_table_indexes(from, to, rename = {}) #:nodoc:
-
indexes(from).each do |index|
-
name = index.name
-
if to == "a#{from}"
-
name = "t#{name}"
-
elsif from == "a#{to}"
-
name = name[1..-1]
-
end
-
-
to_column_names = columns(to).map { |c| c.name }
-
columns = index.columns.map {|c| rename[c] || c }.select do |column|
-
to_column_names.include?(column)
-
end
-
-
unless columns.empty?
-
# index name can't be the same
-
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
-
opts[:unique] = true if index.unique
-
add_index(to, columns, opts)
-
end
-
end
-
end
-
-
1
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
-
column_mappings = Hash[columns.map {|name| [name, name]}]
-
rename.each { |a| column_mappings[a.last] = a.first }
-
from_columns = columns(from).collect {|col| col.name}
-
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
-
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
-
-
quoted_to = quote_table_name(to)
-
-
raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
-
-
exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
-
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
-
-
column_values = columns.map do |col|
-
quote(row[column_mappings[col]], raw_column_mappings[col])
-
end
-
-
sql << column_values * ', '
-
sql << ')'
-
exec_query sql
-
end
-
end
-
-
1
def sqlite_version
-
@sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
-
end
-
-
1
def default_primary_key_type
-
if supports_autoincrement?
-
'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
-
else
-
'INTEGER PRIMARY KEY NOT NULL'
-
end
-
end
-
-
1
def translate_exception(exception, message)
-
case exception.message
-
# SQLite 3.8.2 returns a newly formatted error message:
-
# UNIQUE constraint failed: *table_name*.*column_name*
-
# Older versions of SQLite return:
-
# column *column_name* is not unique
-
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
-
RecordNotUnique.new(message, exception)
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class StatementPool
-
1
include Enumerable
-
-
1
def initialize(connection, max = 1000)
-
1
@connection = connection
-
1
@max = max
-
end
-
-
1
def each
-
raise NotImplementedError
-
end
-
-
1
def key?(key)
-
raise NotImplementedError
-
end
-
-
1
def [](key)
-
raise NotImplementedError
-
end
-
-
1
def length
-
raise NotImplementedError
-
end
-
-
1
def []=(sql, key)
-
raise NotImplementedError
-
end
-
-
1
def clear
-
raise NotImplementedError
-
end
-
-
1
def delete(key)
-
raise NotImplementedError
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionHandling
-
# Establishes the connection to the database. Accepts a hash as input where
-
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
-
# example for regular databases (MySQL, Postgresql, etc):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# adapter: "mysql",
-
# host: "localhost",
-
# username: "myuser",
-
# password: "mypass",
-
# database: "somedatabase"
-
# )
-
#
-
# Example for SQLite database:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# adapter: "sqlite",
-
# database: "path/to/dbfile"
-
# )
-
#
-
# Also accepts keys as strings (for parsing from YAML for example):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "adapter" => "sqlite",
-
# "database" => "path/to/dbfile"
-
# )
-
#
-
# Or a URL:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "postgres://myuser:mypass@localhost/somedatabase"
-
# )
-
#
-
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
-
# may be returned on an error.
-
1
def establish_connection(spec = ENV["DATABASE_URL"])
-
1
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations
-
1
spec = resolver.spec
-
-
1
unless respond_to?(spec.adapter_method)
-
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
-
end
-
-
1
remove_connection
-
1
connection_handler.establish_connection self, spec
-
end
-
-
# Returns the connection currently associated with the class. This can
-
# also be used to "borrow" the connection to do database work unrelated
-
# to any of the specific Active Records.
-
1
def connection
-
13177
retrieve_connection
-
end
-
-
1
def connection_id
-
14233
ActiveRecord::RuntimeRegistry.connection_id
-
end
-
-
1
def connection_id=(connection_id)
-
353
ActiveRecord::RuntimeRegistry.connection_id = connection_id
-
end
-
-
# Returns the configuration of the associated connection as a hash:
-
#
-
# ActiveRecord::Base.connection_config
-
# # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
-
#
-
# Please use only for reading.
-
1
def connection_config
-
connection_pool.spec.config
-
end
-
-
1
def connection_pool
-
connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
-
end
-
-
1
def retrieve_connection
-
13177
connection_handler.retrieve_connection(self)
-
end
-
-
# Returns +true+ if Active Record is connected.
-
1
def connected?
-
617
connection_handler.connected?(self)
-
end
-
-
1
def remove_connection(klass = self)
-
1
connection_handler.remove_connection(klass)
-
end
-
-
1
def clear_cache! # :nodoc:
-
connection.schema_cache.clear!
-
end
-
-
1
delegate :clear_active_connections!, :clear_reloadable_connections!,
-
:clear_all_connections!, :to => :connection_handler
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'thread'
-
-
1
module ActiveRecord
-
1
module Core
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
#
-
# Accepts a logger conforming to the interface of Log4r which is then
-
# passed on to any new database connections made and which can be
-
# retrieved on both a class and instance level by calling +logger+.
-
1
mattr_accessor :logger, instance_writer: false
-
-
##
-
# :singleton-method:
-
# Contains the database configuration - as is typically stored in config/database.yml -
-
# as a Hash.
-
#
-
# For example, the following database.yml...
-
#
-
# development:
-
# adapter: sqlite3
-
# database: db/development.sqlite3
-
#
-
# production:
-
# adapter: sqlite3
-
# database: db/production.sqlite3
-
#
-
# ...would result in ActiveRecord::Base.configurations to look like this:
-
#
-
# {
-
# 'development' => {
-
# 'adapter' => 'sqlite3',
-
# 'database' => 'db/development.sqlite3'
-
# },
-
# 'production' => {
-
# 'adapter' => 'sqlite3',
-
# 'database' => 'db/production.sqlite3'
-
# }
-
# }
-
1
mattr_accessor :configurations, instance_writer: false
-
1
self.configurations = {}
-
-
##
-
# :singleton-method:
-
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
-
# dates and times from the database. This is set to :utc by default.
-
1
mattr_accessor :default_timezone, instance_writer: false
-
1
self.default_timezone = :utc
-
-
##
-
# :singleton-method:
-
# Specifies the format to use when dumping the database schema with Rails'
-
# Rakefile. If :sql, the schema is dumped as (potentially database-
-
# specific) SQL statements. If :ruby, the schema is dumped as an
-
# ActiveRecord::Schema file which can be loaded into any database that
-
# supports migrations. Use :ruby if you want to have different database
-
# adapters for, e.g., your development and test environments.
-
1
mattr_accessor :schema_format, instance_writer: false
-
1
self.schema_format = :ruby
-
-
##
-
# :singleton-method:
-
# Specify whether or not to use timestamps for migration versions
-
1
mattr_accessor :timestamped_migrations, instance_writer: false
-
1
self.timestamped_migrations = true
-
-
##
-
# :singleton-method:
-
# Disable implicit join references. This feature was deprecated with Rails 4.
-
# If you don't make use of implicit references but still see deprecation warnings
-
# you can disable the feature entirely. This will be the default with Rails 4.1.
-
1
mattr_accessor :disable_implicit_join_references, instance_writer: false
-
1
self.disable_implicit_join_references = false
-
-
1
class_attribute :default_connection_handler, instance_writer: false
-
-
1
def self.connection_handler
-
14153
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
-
end
-
-
1
def self.connection_handler=(handler)
-
ActiveRecord::RuntimeRegistry.connection_handler = handler
-
end
-
-
1
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
-
end
-
-
1
module ClassMethods
-
1
def initialize_generated_modules
-
5
super
-
-
5
generated_feature_methods
-
end
-
-
1
def generated_feature_methods
-
@generated_feature_methods ||= begin
-
5
mod = const_set(:GeneratedFeatureMethods, Module.new)
-
5
include mod
-
5
mod
-
61
end
-
end
-
-
# Returns a string like 'Post(id:integer, title:string, body:text)'
-
1
def inspect
-
2
if self == Base
-
super
-
2
elsif abstract_class?
-
"#{super}(abstract)"
-
2
elsif !connected?
-
"#{super} (call '#{super}.connection' to establish a connection)"
-
2
elsif table_exists?
-
20
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
-
2
"#{super}(#{attr_list})"
-
else
-
"#{super}(Table doesn't exist)"
-
end
-
end
-
-
# Overwrite the default class equality method to provide support for association proxies.
-
1
def ===(object)
-
1073
object.is_a?(self)
-
end
-
-
# Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
-
#
-
# class Post < ActiveRecord::Base
-
# scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
-
# end
-
1
def arel_table
-
8757
@arel_table ||= Arel::Table.new(table_name, arel_engine)
-
end
-
-
# Returns the Arel engine.
-
1
def arel_engine
-
@arel_engine ||= begin
-
5
if Base == self || connection_handler.retrieve_connection_pool(self)
-
5
self
-
else
-
superclass.arel_engine
-
end
-
561
end
-
end
-
-
1
private
-
-
1
def relation #:nodoc:
-
4771
relation = Relation.new(self, arel_table)
-
-
4771
if finder_needs_type_condition?
-
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
-
else
-
4771
relation
-
end
-
end
-
end
-
-
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
-
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
-
# In both instances, valid attribute keys are determined by the column names of the associated table --
-
# hence you can't have attributes that aren't part of the table columns.
-
#
-
# ==== Example:
-
# # Instantiates a single new object
-
# User.new(first_name: 'Jamie')
-
1
def initialize(attributes = nil, options = {})
-
454
defaults = self.class.column_defaults.dup
-
3900
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
-
-
454
@attributes = self.class.initialize_attributes(defaults)
-
454
@column_types_override = nil
-
454
@column_types = self.class.column_types
-
-
454
init_internals
-
454
init_changed_attributes
-
454
ensure_proper_type
-
454
populate_with_current_scope_attributes
-
-
# +options+ argument is only needed to make protected_attributes gem easier to hook.
-
# Remove it when we drop support to this gem.
-
454
init_attributes(attributes, options) if attributes
-
-
454
yield self if block_given?
-
454
run_callbacks :initialize unless _initialize_callbacks.empty?
-
end
-
-
# Initialize an empty model object from +coder+. +coder+ must contain
-
# the attributes necessary for initializing an empty model object. For
-
# example:
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
#
-
# post = Post.allocate
-
# post.init_with('attributes' => { 'title' => 'hello world' })
-
# post.title # => 'hello world'
-
1
def init_with(coder)
-
608
@attributes = self.class.initialize_attributes(coder['attributes'])
-
608
@column_types_override = coder['column_types']
-
608
@column_types = self.class.column_types
-
-
608
init_internals
-
-
608
@new_record = false
-
-
608
run_callbacks :find
-
608
run_callbacks :initialize
-
-
608
self
-
end
-
-
##
-
# :method: clone
-
# Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
-
# That means that modifying attributes of the clone will modify the original, since they will both point to the
-
# same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
-
#
-
# user = User.first
-
# new_user = user.clone
-
# user.name # => "Bob"
-
# new_user.name = "Joe"
-
# user.name # => "Joe"
-
#
-
# user.object_id == new_user.object_id # => false
-
# user.name.object_id == new_user.name.object_id # => true
-
#
-
# user.name.object_id == user.dup.name.object_id # => false
-
-
##
-
# :method: dup
-
# Duped objects have no id assigned and are treated as new records. Note
-
# that this is a "shallow" copy as it copies the object's attributes
-
# only, not its associations. The extent of a "deep" copy is application
-
# specific and is therefore left to the application to implement according
-
# to its need.
-
# The dup method does not preserve the timestamps (created|updated)_(at|on).
-
-
##
-
1
def initialize_dup(other) # :nodoc:
-
1
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
-
1
self.class.initialize_attributes(cloned_attributes, :serialized => false)
-
-
1
@attributes = cloned_attributes
-
1
@attributes[self.class.primary_key] = nil
-
-
1
run_callbacks(:initialize) unless _initialize_callbacks.empty?
-
-
1
@changed_attributes = {}
-
1
init_changed_attributes
-
-
1
@aggregation_cache = {}
-
1
@association_cache = {}
-
1
@attributes_cache = {}
-
-
1
@new_record = true
-
-
1
ensure_proper_type
-
1
super
-
end
-
-
# Populate +coder+ with attributes about this record that should be
-
# serialized. The structure of +coder+ defined in this method is
-
# guaranteed to match the structure of +coder+ passed to the +init_with+
-
# method.
-
#
-
# Example:
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
# coder = {}
-
# Post.new.encode_with(coder)
-
# coder # => {"attributes" => {"id" => nil, ... }}
-
1
def encode_with(coder)
-
coder['attributes'] = attributes_for_coder
-
end
-
-
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
-
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
-
#
-
# Note that new records are different from any other record by definition, unless the
-
# other record is the receiver itself. Besides, if you fetch existing records with
-
# +select+ and leave the ID out, you're on your own, this predicate will return false.
-
#
-
# Note also that destroying a record preserves its ID in the model instance, so deleted
-
# models are still comparable.
-
1
def ==(comparison_object)
-
super ||
-
comparison_object.instance_of?(self.class) &&
-
id.present? &&
-
657
comparison_object.id == id
-
end
-
1
alias :eql? :==
-
-
# Delegates to id in order to allow two records of the same type and id to work with something like:
-
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
-
1
def hash
-
2
id.hash
-
end
-
-
# Clone and freeze the attributes hash such that associations are still
-
# accessible, even on destroyed records, but cloned models will not be
-
# frozen.
-
1
def freeze
-
12
@attributes = @attributes.clone.freeze
-
12
self
-
end
-
-
# Returns +true+ if the attributes hash has been frozen.
-
1
def frozen?
-
@attributes.frozen?
-
end
-
-
# Allows sort on objects
-
1
def <=>(other_object)
-
if other_object.is_a?(self.class)
-
self.to_key <=> other_object.to_key
-
end
-
end
-
-
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
-
# attributes will be marked as read only since they cannot be saved.
-
1
def readonly?
-
399
@readonly
-
end
-
-
# Marks this record as read only.
-
1
def readonly!
-
@readonly = true
-
end
-
-
# Returns the connection currently associated with the class. This can
-
# also be used to "borrow" the connection to do database work that isn't
-
# easily done without going straight to SQL.
-
1
def connection
-
ActiveSupport::Deprecation.warn("#connection is deprecated in favour of accessing it via the class")
-
self.class.connection
-
end
-
-
1
def connection_handler
-
self.class.connection_handler
-
end
-
-
# Returns the contents of the record as a nicely formatted string.
-
1
def inspect
-
# We check defined?(@attributes) not to issue warnings if the object is
-
# allocated but not initialized.
-
13
inspection = if defined?(@attributes) && @attributes
-
self.class.column_names.collect { |name|
-
105
if has_attribute?(name)
-
105
"#{name}: #{attribute_for_inspect(name)}"
-
end
-
13
}.compact.join(", ")
-
else
-
"not initialized"
-
end
-
13
"#<#{self.class} #{inspection}>"
-
end
-
-
# Returns a hash of the given methods with their names as keys and returned values as values.
-
1
def slice(*methods)
-
Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access
-
end
-
-
1
def set_transaction_state(state) # :nodoc:
-
102
@transaction_state = state
-
end
-
-
1
def has_transactional_callbacks? # :nodoc:
-
417
!_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_create_callbacks.empty?
-
end
-
-
# Required to deserialize Syck properly.
-
1
if YAML.const_defined?(:ENGINE) && YAML::ENGINE.syck?
-
ActiveSupport::Deprecation.warn(
-
"Syck is deprecated and support for serialization has been removed." \
-
" ActiveRecord::Core#yaml_initialize will be removed in 4.1 which will break deserialization support with Syck."
-
)
-
def yaml_initialize(tag, coder) # :nodoc:
-
init_with(coder)
-
end
-
end
-
-
1
private
-
-
# Updates the attributes on this particular ActiveRecord object so that
-
# if it is associated with a transaction, then the state of the AR object
-
# will be updated to reflect the current state of the transaction
-
#
-
# The @transaction_state variable stores the states of the associated
-
# transaction. This relies on the fact that a transaction can only be in
-
# one rollback or commit (otherwise a list of states would be required)
-
# Each AR object inside of a transaction carries that transaction's
-
# TransactionState.
-
#
-
# This method checks to see if the ActiveRecord object's state reflects
-
# the TransactionState, and rolls back or commits the ActiveRecord object
-
# as appropriate.
-
#
-
# Since ActiveRecord objects can be inside multiple transactions, this
-
# method recursively goes through the parent of the TransactionState and
-
# checks if the ActiveRecord object reflects the state of the object.
-
1
def sync_with_transaction_state
-
3796
update_attributes_from_transaction_state(@transaction_state, 0)
-
end
-
-
1
def update_attributes_from_transaction_state(transaction_state, depth)
-
3796
if transaction_state && transaction_state.finalized? && !has_transactional_callbacks?
-
unless @reflects_state[depth]
-
restore_transaction_record_state if transaction_state.rolledback?
-
clear_transaction_record_state
-
@reflects_state[depth] = true
-
end
-
-
if transaction_state.parent && !@reflects_state[depth+1]
-
update_attributes_from_transaction_state(transaction_state.parent, depth+1)
-
end
-
end
-
end
-
-
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
-
# of the array, and then rescues from the possible NoMethodError. If those elements are
-
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
-
# which significantly impacts upon performance.
-
#
-
# So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
-
#
-
# See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
-
1
def to_ary # :nodoc:
-
nil
-
end
-
-
1
def init_internals
-
1062
pk = self.class.primary_key
-
1062
@attributes[pk] = nil unless @attributes.key?(pk)
-
-
1062
@aggregation_cache = {}
-
1062
@association_cache = {}
-
1062
@attributes_cache = {}
-
1062
@previously_changed = {}
-
1062
@changed_attributes = {}
-
1062
@readonly = false
-
1062
@destroyed = false
-
1062
@marked_for_destruction = false
-
1062
@destroyed_by_association = nil
-
1062
@new_record = true
-
1062
@txn = nil
-
1062
@_start_transaction_state = {}
-
1062
@transaction_state = nil
-
1062
@reflects_state = [false]
-
end
-
-
1
def init_changed_attributes
-
# Intentionally avoid using #column_defaults since overridden defaults (as is done in
-
# optimistic locking) won't get written unless they get marked as changed
-
455
self.class.columns.each do |c|
-
3455
attr, orig_value = c.name, c.default
-
3455
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
-
end
-
end
-
-
# This method is needed to make protected_attributes gem easier to hook.
-
# Remove it when we drop support to this gem.
-
1
def init_attributes(attributes, options)
-
218
assign_attributes(attributes)
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Counter Cache
-
1
module CounterCache
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Resets one or more counter caches to their correct value using an SQL
-
# count query. This is useful when adding new counter caches, or if the
-
# counter has been corrupted or modified directly by SQL.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to reset a counter on.
-
# * +counters+ - One or more association counters to reset
-
#
-
# ==== Examples
-
#
-
# # For Post with id #1 records reset the comments_count
-
# Post.reset_counters(1, :comments)
-
1
def reset_counters(id, *counters)
-
object = find(id)
-
counters.each do |association|
-
has_many_association = reflect_on_association(association.to_sym)
-
raise ArgumentError, "'#{self.name}' has no association called '#{association}'" unless has_many_association
-
-
if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
-
has_many_association = has_many_association.through_reflection
-
end
-
-
foreign_key = has_many_association.foreign_key.to_s
-
child_class = has_many_association.klass
-
belongs_to = child_class.reflect_on_all_associations(:belongs_to)
-
reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
-
counter_name = reflection.counter_cache_column
-
-
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
-
arel_table[counter_name] => object.send(association).count
-
})
-
connection.update stmt
-
end
-
return true
-
end
-
-
# A generic "counter updater" implementation, intended primarily to be
-
# used by increment_counter and decrement_counter, but which may also
-
# be useful on its own. It simply does a direct SQL update for the record
-
# with the given ID, altering the given hash of counters by the amount
-
# given by the corresponding value:
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to update a counter on or an Array of ids.
-
# * +counters+ - An Array of Hashes containing the names of the fields
-
# to update as keys and the amount to update the field by as values.
-
#
-
# ==== Examples
-
#
-
# # For the Post with id of 5, decrement the comment_count by 1, and
-
# # increment the action_count by 1
-
# Post.update_counters 5, comment_count: -1, action_count: 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) - 1,
-
# # action_count = COALESCE(action_count, 0) + 1
-
# # WHERE id = 5
-
#
-
# # For the Posts with id of 10 and 15, increment the comment_count by 1
-
# Post.update_counters [10, 15], comment_count: 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) + 1
-
# # WHERE id IN (10, 15)
-
1
def update_counters(id, counters)
-
updates = counters.map do |counter_name, value|
-
operator = value < 0 ? '-' : '+'
-
quoted_column = connection.quote_column_name(counter_name)
-
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
-
end
-
-
unscoped.where(primary_key => id).update_all updates.join(', ')
-
end
-
-
# Increment a numeric field by one, via a direct SQL update.
-
#
-
# This method is used primarily for maintaining counter_cache columns used to
-
# store aggregate values. For example, a DiscussionBoard may cache posts_count
-
# and comments_count to avoid running an SQL query to calculate the number of
-
# posts and comments there are each time it is displayed.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be incremented.
-
# * +id+ - The id of the object that should be incremented or an Array of ids.
-
#
-
# ==== Examples
-
#
-
# # Increment the post_count column for the record with an id of 5
-
# DiscussionBoard.increment_counter(:post_count, 5)
-
1
def increment_counter(counter_name, id)
-
update_counters(id, counter_name => 1)
-
end
-
-
# Decrement a numeric field by one, via a direct SQL update.
-
#
-
# This works the same as increment_counter but reduces the column value by
-
# 1 instead of increasing it.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be decremented.
-
# * +id+ - The id of the object that should be decremented or an Array of ids.
-
#
-
# ==== Examples
-
#
-
# # Decrement the post_count column for the record with an id of 5
-
# DiscussionBoard.decrement_counter(:post_count, 5)
-
1
def decrement_counter(counter_name, id)
-
update_counters(id, counter_name => -1)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module DynamicMatchers #:nodoc:
-
# This code in this file seems to have a lot of indirection, but the indirection
-
# is there to provide extension points for the activerecord-deprecated_finders
-
# gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
-
# then we can remove the indirection.
-
-
1
def respond_to?(name, include_private = false)
-
602
match = Method.match(self, name)
-
602
match && match.valid? || super
-
end
-
-
1
private
-
-
1
def method_missing(name, *arguments, &block)
-
match = Method.match(self, name)
-
-
if match && match.valid?
-
match.define
-
send(name, *arguments, &block)
-
else
-
super
-
end
-
end
-
-
1
class Method
-
1
@matchers = []
-
-
1
class << self
-
1
attr_reader :matchers
-
-
1
def match(model, name)
-
5436
klass = matchers.find { |k| name =~ k.pattern }
-
604
klass.new(model, name) if klass
-
end
-
-
1
def pattern
-
4832
@pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
-
end
-
-
1
def prefix
-
raise NotImplementedError
-
end
-
-
1
def suffix
-
6
''
-
end
-
end
-
-
1
attr_reader :model, :name, :attribute_names
-
-
1
def initialize(model, name)
-
@model = model
-
@name = name.to_s
-
@attribute_names = @name.match(self.class.pattern)[1].split('_and_')
-
@attribute_names.map! { |n| @model.attribute_aliases[n] || n }
-
end
-
-
1
def valid?
-
attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
-
end
-
-
1
def define
-
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def self.#{name}(#{signature})
-
#{body}
-
end
-
CODE
-
end
-
-
1
def body
-
raise NotImplementedError
-
end
-
end
-
-
1
module Finder
-
# Extended in activerecord-deprecated_finders
-
1
def body
-
result
-
end
-
-
# Extended in activerecord-deprecated_finders
-
1
def result
-
"#{finder}(#{attributes_hash})"
-
end
-
-
# The parameters in the signature may have reserved Ruby words, in order
-
# to prevent errors, we start each param name with `_`.
-
#
-
# Extended in activerecord-deprecated_finders
-
1
def signature
-
attribute_names.map { |name| "_#{name}" }.join(', ')
-
end
-
-
# Given that the parameters starts with `_`, the finder needs to use the
-
# same parameter name.
-
1
def attributes_hash
-
"{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}"
-
end
-
-
1
def finder
-
raise NotImplementedError
-
end
-
end
-
-
1
class FindBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
-
1
def self.prefix
-
1
"find_by"
-
end
-
-
1
def finder
-
"find_by"
-
end
-
end
-
-
1
class FindByBang < Method
-
1
Method.matchers << self
-
1
include Finder
-
-
1
def self.prefix
-
1
"find_by"
-
end
-
-
1
def self.suffix
-
1
"!"
-
end
-
-
1
def finder
-
"find_by!"
-
end
-
end
-
end
-
end
-
1
require 'active_support/notifications'
-
1
require 'active_record/explain_registry'
-
-
1
module ActiveRecord
-
1
class ExplainSubscriber # :nodoc:
-
1
def start(name, id, payload)
-
# unused
-
end
-
-
1
def finish(name, id, payload)
-
3219
if ExplainRegistry.collect? && !ignore_payload?(payload)
-
ExplainRegistry.queries << payload.values_at(:sql, :binds)
-
end
-
end
-
-
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
-
# our own EXPLAINs now matter how loopingly beautiful that would be.
-
#
-
# On the other hand, we want to monitor the performance of our real database
-
# queries, not the performance of the access to the query cache.
-
1
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
-
1
EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i
-
1
def ignore_payload?(payload)
-
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
-
end
-
-
1
ActiveSupport::Notifications.subscribe("sql.active_record", new)
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
1
module Inheritance
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Determine whether to store the full constant name including namespace when using STI
-
1
class_attribute :store_full_sti_class, instance_writer: false
-
1
self.store_full_sti_class = true
-
end
-
-
1
module ClassMethods
-
# Determines if one of the attributes passed in is the inheritance column,
-
# and if the inheritance column is attr accessible, it initializes an
-
# instance of the given subclass instead of the base class
-
1
def new(*args, &block)
-
454
if abstract_class? || self == Base
-
raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
-
end
-
-
454
attrs = args.first
-
454
if subclass_from_attributes?(attrs)
-
subclass = subclass_from_attributes(attrs)
-
end
-
-
454
if subclass
-
subclass.new(*args, &block)
-
else
-
454
super
-
end
-
end
-
-
# True if this isn't a concrete subclass needing a STI type condition.
-
1
def descends_from_active_record?
-
5
if self == Base
-
false
-
5
elsif superclass.abstract_class?
-
superclass.descends_from_active_record?
-
else
-
5
superclass == Base || !columns_hash.include?(inheritance_column)
-
end
-
end
-
-
1
def finder_needs_type_condition? #:nodoc:
-
# This is like this because benchmarking justifies the strange :false stuff
-
5226
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
-
end
-
-
1
def symbolized_base_class
-
@symbolized_base_class ||= base_class.to_s.to_sym
-
end
-
-
1
def symbolized_sti_name
-
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
-
end
-
-
# Returns the class descending directly from ActiveRecord::Base, or
-
# an abstract class, if any, in the inheritance hierarchy.
-
#
-
# If A extends AR::Base, A.base_class will return A. If B descends from A
-
# through some arbitrarily deep hierarchy, B.base_class will return A.
-
#
-
# If B < A and C < B and if A is an abstract_class then both B.base_class
-
# and C.base_class would return B as the answer since A is an abstract_class.
-
1
def base_class
-
7942
unless self < Base
-
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
-
end
-
-
7942
if superclass == Base || superclass.abstract_class?
-
7942
self
-
else
-
superclass.base_class
-
end
-
end
-
-
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
-
# If you are using inheritance with ActiveRecord and don't want child classes
-
# to utilize the implied STI table name of the parent class, this will need to be true.
-
# For example, given the following:
-
#
-
# class SuperClass < ActiveRecord::Base
-
# self.abstract_class = true
-
# end
-
# class Child < SuperClass
-
# self.table_name = 'the_table_i_really_want'
-
# end
-
#
-
#
-
# <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
-
#
-
1
attr_accessor :abstract_class
-
-
# Returns whether this class is an abstract class or not.
-
1
def abstract_class?
-
987
defined?(@abstract_class) && @abstract_class == true
-
end
-
-
1
def sti_name
-
store_full_sti_class ? name : name.demodulize
-
end
-
-
1
protected
-
-
# Returns the class type of the record using the current module as a prefix. So descendants of
-
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
-
1
def compute_type(type_name)
-
14
if type_name.match(/^::/)
-
# If the type is prefixed with a scope operator then we assume that
-
# the type_name is an absolute reference.
-
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
# Build a list of candidates to search for
-
14
candidates = []
-
28
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
-
14
candidates << type_name
-
-
14
candidates.each do |candidate|
-
28
begin
-
28
constant = ActiveSupport::Dependencies.constantize(candidate)
-
14
return constant if candidate == constant.to_s
-
rescue NameError => e
-
# We don't want to swallow NoMethodError < NameError errors
-
14
raise e unless e.instance_of?(NameError)
-
end
-
end
-
-
raise NameError, "uninitialized constant #{candidates.first}"
-
end
-
end
-
-
1
private
-
-
# Called by +instantiate+ to decide which class to use for a new
-
# record instance. For single-table inheritance, we check the record
-
# for a +type+ column and return the corresponding class.
-
1
def discriminate_class_for_record(record)
-
608
if using_single_table_inheritance?(record)
-
find_sti_class(record[inheritance_column])
-
else
-
608
super
-
end
-
end
-
-
1
def using_single_table_inheritance?(record)
-
608
record[inheritance_column].present? && columns_hash.include?(inheritance_column)
-
end
-
-
1
def find_sti_class(type_name)
-
if store_full_sti_class
-
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
compute_type(type_name)
-
end
-
rescue NameError
-
raise SubclassNotFound,
-
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
-
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
-
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
-
"or overwrite #{name}.inheritance_column to use another column for that information."
-
end
-
-
1
def type_condition(table = arel_table)
-
sti_column = table[inheritance_column.to_sym]
-
sti_names = ([self] + descendants).map { |model| model.sti_name }
-
-
sti_column.in(sti_names)
-
end
-
-
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
-
# is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
-
# If this is a StrongParameters hash, and access to inheritance_column is not permitted,
-
# this will ignore the inheritance column and return nil
-
1
def subclass_from_attributes?(attrs)
-
454
columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
-
end
-
-
1
def subclass_from_attributes(attrs)
-
subclass_name = attrs.with_indifferent_access[inheritance_column]
-
-
if subclass_name.present? && subclass_name != self.name
-
subclass = subclass_name.safe_constantize
-
-
unless descendants.include?(subclass)
-
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
-
end
-
-
subclass
-
end
-
end
-
end
-
-
1
private
-
-
# Sets the attribute used for single table inheritance to this class name if this is not the
-
# ActiveRecord::Base descendant.
-
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
-
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
-
# No such attribute would be set for objects of the Message class in that example.
-
1
def ensure_proper_type
-
455
klass = self.class
-
455
if klass.finder_needs_type_condition?
-
write_attribute(klass.inheritance_column, klass.sti_name)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Integration
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
# Indicates the format used to generate the timestamp in the cache key.
-
# Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
-
#
-
# This is +:nsec+, by default.
-
1
class_attribute :cache_timestamp_format, :instance_writer => false
-
1
self.cache_timestamp_format = :nsec
-
end
-
-
# Returns a String, which Action Pack uses for constructing an URL to this
-
# object. The default implementation returns this record's id as a String,
-
# or nil if this record's unsaved.
-
#
-
# For example, suppose that you have a User model, and that you have a
-
# <tt>resources :users</tt> route. Normally, +user_path+ will
-
# construct a path with the user object's 'id' in it:
-
#
-
# user = User.find_by(name: 'Phusion')
-
# user_path(user) # => "/users/1"
-
#
-
# You can override +to_param+ in your model to make +user_path+ construct
-
# a path using the user's name instead of the user's id:
-
#
-
# class User < ActiveRecord::Base
-
# def to_param # overridden
-
# name
-
# end
-
# end
-
#
-
# user = User.find_by(name: 'Phusion')
-
# user_path(user) # => "/users/Phusion"
-
1
def to_param
-
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
-
998
id && id.to_s # Be sure to stringify the id for routes
-
end
-
-
# Returns a cache key that can be used to identify this record.
-
#
-
# Product.new.cache_key # => "products/new"
-
# Product.find(5).cache_key # => "products/5" (updated_at not available)
-
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
-
1
def cache_key
-
case
-
when new_record?
-
"#{self.class.model_name.cache_key}/new"
-
when timestamp = max_updated_column_timestamp
-
timestamp = timestamp.utc.to_s(cache_timestamp_format)
-
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
-
else
-
"#{self.class.model_name.cache_key}/#{id}"
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Locking
-
# == What is Optimistic Locking
-
#
-
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
-
# conflicts with the data. It does this by checking whether another process has made changes to a record since
-
# it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
-
# and the update is ignored.
-
#
-
# Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
-
#
-
# == Usage
-
#
-
# Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
-
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
-
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.first_name = "should fail"
-
# p2.save # Raises a ActiveRecord::StaleObjectError
-
#
-
# Optimistic locking will also check for stale data when objects are destroyed. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.destroy # Raises a ActiveRecord::StaleObjectError
-
#
-
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
-
# or otherwise apply the business logic needed to resolve the conflict.
-
#
-
# This locking mechanism will function inside a single Ruby process. To make it work across all
-
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
-
#
-
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
-
# To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
-
#
-
# class Person < ActiveRecord::Base
-
# self.locking_column = :lock_person
-
# end
-
#
-
1
module Optimistic
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :lock_optimistically, instance_writer: false
-
1
self.lock_optimistically = true
-
end
-
-
1
def locking_enabled? #:nodoc:
-
120
self.class.locking_enabled?
-
end
-
-
1
private
-
1
def increment_lock
-
lock_col = self.class.locking_column
-
previous_lock_value = send(lock_col).to_i
-
send(lock_col + '=', previous_lock_value + 1)
-
end
-
-
1
def update_record(attribute_names = @attributes.keys) #:nodoc:
-
96
return super unless locking_enabled?
-
return 0 if attribute_names.empty?
-
-
lock_col = self.class.locking_column
-
previous_lock_value = send(lock_col).to_i
-
increment_lock
-
-
attribute_names += [lock_col]
-
attribute_names.uniq!
-
-
begin
-
relation = self.class.unscoped
-
-
stmt = relation.where(
-
relation.table[self.class.primary_key].eq(id).and(
-
relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
-
)
-
).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
-
-
affected_rows = self.class.connection.update stmt
-
-
unless affected_rows == 1
-
raise ActiveRecord::StaleObjectError.new(self, "update")
-
end
-
-
affected_rows
-
-
# If something went wrong, revert the version.
-
rescue Exception
-
send(lock_col + '=', previous_lock_value)
-
raise
-
end
-
end
-
-
1
def destroy_row
-
12
affected_rows = super
-
-
12
if locking_enabled? && affected_rows != 1
-
raise ActiveRecord::StaleObjectError.new(self, "destroy")
-
end
-
-
12
affected_rows
-
end
-
-
1
def relation_for_destroy
-
12
relation = super
-
-
12
if locking_enabled?
-
column_name = self.class.locking_column
-
column = self.class.columns_hash[column_name]
-
substitute = self.class.connection.substitute_at(column, relation.bind_values.length)
-
-
relation = relation.where(self.class.arel_table[column_name].eq(substitute))
-
relation.bind_values << [column, self[column_name].to_i]
-
end
-
-
12
relation
-
end
-
-
1
module ClassMethods
-
1
DEFAULT_LOCKING_COLUMN = 'lock_version'
-
-
# Returns true if the +lock_optimistically+ flag is set to true
-
# (which it is, by default) and the table includes the
-
# +locking_column+ column (defaults to +lock_version+).
-
1
def locking_enabled?
-
120
lock_optimistically && columns_hash[locking_column]
-
end
-
-
# Set the column to use for optimistic locking. Defaults to +lock_version+.
-
1
def locking_column=(value)
-
4
@locking_column = value.to_s
-
end
-
-
# The version column used for optimistic locking. Defaults to +lock_version+.
-
1
def locking_column
-
124
reset_locking_column unless defined?(@locking_column)
-
124
@locking_column
-
end
-
-
# Quote the column name used for optimistic locking.
-
1
def quoted_locking_column
-
connection.quote_column_name(locking_column)
-
end
-
-
# Reset the column used for optimistic locking back to the +lock_version+ default.
-
1
def reset_locking_column
-
4
self.locking_column = DEFAULT_LOCKING_COLUMN
-
end
-
-
# Make sure the lock version column gets updated when counters are
-
# updated.
-
1
def update_counters(id, counters)
-
counters = counters.merge(locking_column => 1) if locking_enabled?
-
super
-
end
-
-
1
def column_defaults
-
@column_defaults ||= begin
-
4
defaults = super
-
-
4
if defaults.key?(locking_column) && lock_optimistically
-
defaults[locking_column] ||= 0
-
end
-
-
4
defaults
-
454
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Locking
-
# Locking::Pessimistic provides support for row-level locking using
-
# SELECT ... FOR UPDATE and other lock types.
-
#
-
# Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
-
# lock on the selected rows:
-
# # select * from accounts where id=1 for update
-
# Account.lock.find(1)
-
#
-
# Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
-
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where name = 'shugo' limit 1 for update
-
# shugo = Account.where("name = 'shugo'").lock(true).first
-
# yuko = Account.where("name = 'yuko'").lock(true).first
-
# shugo.balance -= 100
-
# shugo.save!
-
# yuko.balance += 100
-
# yuko.save!
-
# end
-
#
-
# You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
-
# This may be better if you don't need to lock every row. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where ...
-
# accounts = Account.where(...)
-
# account1 = accounts.detect { |account| ... }
-
# account2 = accounts.detect { |account| ... }
-
# # select * from accounts where id=? for update
-
# account1.lock!
-
# account2.lock!
-
# account1.balance -= 100
-
# account1.save!
-
# account2.balance += 100
-
# account2.save!
-
# end
-
#
-
# You can start a transaction and acquire the lock in one go by calling
-
# <tt>with_lock</tt> with a block. The block is called from within
-
# a transaction, the object is already locked. Example:
-
#
-
# account = Account.first
-
# account.with_lock do
-
# # This block is called within a transaction,
-
# # account is already locked.
-
# account.balance -= 100
-
# account.save!
-
# end
-
#
-
# Database-specific information on row locking:
-
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
-
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
-
1
module Pessimistic
-
# Obtain a row lock on this record. Reloads the record to obtain the requested
-
# lock. Pass an SQL locking clause to append the end of the SELECT statement
-
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
-
# the locked record.
-
1
def lock!(lock = true)
-
reload(:lock => lock) if persisted?
-
self
-
end
-
-
# Wraps the passed block in a transaction, locking the object
-
# before yielding. You pass can the SQL locking clause
-
# as argument (see <tt>lock!</tt>).
-
1
def with_lock(lock = true)
-
transaction do
-
lock!(lock)
-
yield
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ModelSchema
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
# Accessor for the prefix type that will be prepended to every primary key column name.
-
# The options are :table_name and :table_name_with_underscore. If the first is specified,
-
# the Product class will look for "productid" instead of "id" as the primary column. If the
-
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
-
# that this is a global setting for all Active Records.
-
1
mattr_accessor :primary_key_prefix_type, instance_writer: false
-
-
##
-
# :singleton-method:
-
# Accessor for the name of the prefix string to prepend to every table name. So if set
-
# to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
-
# etc. This is a convenient way of creating a namespace for tables in a shared database.
-
# By default, the prefix is the empty string.
-
#
-
# If you are organising your models within modules you can add a prefix to the models within
-
# a namespace by defining a singleton method in the parent module called table_name_prefix which
-
# returns your chosen prefix.
-
1
class_attribute :table_name_prefix, instance_writer: false
-
1
self.table_name_prefix = ""
-
-
##
-
# :singleton-method:
-
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
-
# "people_basecamp"). By default, the suffix is the empty string.
-
1
class_attribute :table_name_suffix, instance_writer: false
-
1
self.table_name_suffix = ""
-
-
##
-
# :singleton-method:
-
# Indicates whether table names should be the pluralized versions of the corresponding class names.
-
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
-
# See table_name for the full rules on table/class naming. This is true, by default.
-
1
class_attribute :pluralize_table_names, instance_writer: false
-
1
self.pluralize_table_names = true
-
-
1
self.inheritance_column = 'type'
-
end
-
-
1
module ClassMethods
-
# Guesses the table name (in forced lower-case) based on the name of the class in the
-
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
-
# looks like: Reply < Message < ActiveRecord::Base, then Message is used
-
# to guess the table name even when called on Reply. The rules used to do the guess
-
# are handled by the Inflector class in Active Support, which knows almost all common
-
# English inflections. You can add new inflections in config/initializers/inflections.rb.
-
#
-
# Nested classes are given table names prefixed by the singular form of
-
# the parent's table name. Enclosing modules are not considered.
-
#
-
# ==== Examples
-
#
-
# class Invoice < ActiveRecord::Base
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice invoices
-
#
-
# class Invoice < ActiveRecord::Base
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice::Lineitem invoice_lineitems
-
#
-
# module Invoice
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice/lineitem.rb Invoice::Lineitem lineitems
-
#
-
# Additionally, the class-level +table_name_prefix+ is prepended and the
-
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
-
# the table name guess for an Invoice class becomes "myapp_invoices".
-
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
-
#
-
# You can also set your own table name explicitly:
-
#
-
# class Mouse < ActiveRecord::Base
-
# self.table_name = "mice"
-
# end
-
#
-
# Alternatively, you can override the table_name method to define your
-
# own computation. (Possibly using <tt>super</tt> to manipulate the default
-
# table name.) Example:
-
#
-
# class Post < ActiveRecord::Base
-
# def self.table_name
-
# "special_" + super
-
# end
-
# end
-
# Post.table_name # => "special_posts"
-
1
def table_name
-
3909
reset_table_name unless defined?(@table_name)
-
3909
@table_name
-
end
-
-
# Sets the table name explicitly. Example:
-
#
-
# class Project < ActiveRecord::Base
-
# self.table_name = "project"
-
# end
-
#
-
# You can also just define your own <tt>self.table_name</tt> method; see
-
# the documentation for ActiveRecord::Base#table_name.
-
1
def table_name=(value)
-
4
value = value && value.to_s
-
-
4
if defined?(@table_name)
-
return if value == @table_name
-
reset_column_information if connected?
-
end
-
-
4
@table_name = value
-
4
@quoted_table_name = nil
-
4
@arel_table = nil
-
4
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
-
4
@relation = Relation.new(self, arel_table)
-
end
-
-
# Returns a quoted version of the table name, used to construct SQL statements.
-
1
def quoted_table_name
-
@quoted_table_name ||= connection.quote_table_name(table_name)
-
end
-
-
# Computes the table name, (re)sets it internally, and returns it.
-
1
def reset_table_name #:nodoc:
-
4
self.table_name = if abstract_class?
-
superclass == Base ? nil : superclass.table_name
-
elsif superclass.abstract_class?
-
superclass.table_name || compute_table_name
-
else
-
4
compute_table_name
-
end
-
end
-
-
1
def full_table_name_prefix #:nodoc:
-
8
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
-
end
-
-
# Defines the name of the table column which will store the class name on single-table
-
# inheritance situations.
-
#
-
# The default inheritance column name is +type+, which means it's a
-
# reserved word inside Active Record. To be able to use single-table
-
# inheritance with another column name, or to use the column +type+ in
-
# your own model for something else, you can set +inheritance_column+:
-
#
-
# self.inheritance_column = 'zoink'
-
1
def inheritance_column
-
2124
(@inheritance_column ||= nil) || superclass.inheritance_column
-
end
-
-
# Sets the value of inheritance_column
-
1
def inheritance_column=(value)
-
1
@inheritance_column = value.to_s
-
1
@explicit_inheritance_column = true
-
end
-
-
1
def sequence_name
-
if base_class == self
-
@sequence_name ||= reset_sequence_name
-
else
-
(@sequence_name ||= nil) || base_class.sequence_name
-
end
-
end
-
-
1
def reset_sequence_name #:nodoc:
-
@explicit_sequence_name = false
-
@sequence_name = connection.default_sequence_name(table_name, primary_key)
-
end
-
-
# Sets the name of the sequence to use when generating ids to the given
-
# value, or (if the value is nil or false) to the value returned by the
-
# given block. This is required for Oracle and is useful for any
-
# database which relies on sequences for primary key generation.
-
#
-
# If a sequence name is not explicitly set when using Oracle or Firebird,
-
# it will default to the commonly used pattern of: #{table_name}_seq
-
#
-
# If a sequence name is not explicitly set when using PostgreSQL, it
-
# will discover the sequence corresponding to your primary key for you.
-
#
-
# class Project < ActiveRecord::Base
-
# self.sequence_name = "projectseq" # default would have been "project_seq"
-
# end
-
1
def sequence_name=(value)
-
@sequence_name = value.to_s
-
@explicit_sequence_name = true
-
end
-
-
# Indicates whether the table associated with this class exists
-
1
def table_exists?
-
7
connection.schema_cache.table_exists?(table_name)
-
end
-
-
# Returns an array of column objects for the table associated with this class.
-
1
def columns
-
@columns ||= connection.schema_cache.columns(table_name).map do |col|
-
27
col = col.dup
-
27
col.primary = (col.name == primary_key)
-
27
col
-
476
end
-
end
-
-
# Returns a hash of column objects for the table associated with this class.
-
1
def columns_hash
-
16598
@columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
-
end
-
-
1
def column_types # :nodoc:
-
1066
@column_types ||= decorate_columns(columns_hash.dup)
-
end
-
-
1
def decorate_columns(columns_hash) # :nodoc:
-
613
return if columns_hash.empty?
-
-
@serialized_column_names ||= self.columns_hash.keys.find_all do |name|
-
27
serialized_attributes.key?(name)
-
5
end
-
-
5
@serialized_column_names.each do |name|
-
columns_hash[name] = AttributeMethods::Serialization::Type.new(columns_hash[name])
-
end
-
-
@time_zone_column_names ||= self.columns_hash.find_all do |name, col|
-
27
create_time_zone_conversion_attribute?(name, col)
-
5
end.map!(&:first)
-
-
5
@time_zone_column_names.each do |name|
-
8
columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(columns_hash[name])
-
end
-
-
5
columns_hash
-
end
-
-
# Returns a hash where the keys are column names and the values are
-
# default values when instantiating the AR object for this table.
-
1
def column_defaults
-
30
@column_defaults ||= Hash[columns.map { |c| [c.name, c.default] }]
-
end
-
-
# Returns an array of column names as strings.
-
1
def column_names
-
8711
@column_names ||= columns.map { |column| column.name }
-
end
-
-
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
-
# and columns used for single table inheritance have been removed.
-
1
def content_columns
-
@content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
-
end
-
-
# Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
-
# and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
-
# is available.
-
1
def column_methods_hash #:nodoc:
-
@dynamic_methods_hash ||= column_names.each_with_object(Hash.new(false)) do |attr, methods|
-
attr_name = attr.to_s
-
methods[attr.to_sym] = attr_name
-
methods["#{attr}=".to_sym] = attr_name
-
methods["#{attr}?".to_sym] = attr_name
-
methods["#{attr}_before_type_cast".to_sym] = attr_name
-
end
-
end
-
-
# Resets all the cached information about columns, which will cause them
-
# to be reloaded on the next request.
-
#
-
# The most common usage pattern for this method is probably in a migration,
-
# when just after creating a table you want to populate it with some default
-
# values, eg:
-
#
-
# class CreateJobLevels < ActiveRecord::Migration
-
# def up
-
# create_table :job_levels do |t|
-
# t.integer :id
-
# t.string :name
-
#
-
# t.timestamps
-
# end
-
#
-
# JobLevel.reset_column_information
-
# %w{assistant executive manager director}.each do |type|
-
# JobLevel.create(name: type)
-
# end
-
# end
-
#
-
# def down
-
# drop_table :job_levels
-
# end
-
# end
-
1
def reset_column_information
-
connection.clear_cache!
-
undefine_attribute_methods
-
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
-
-
@arel_engine = nil
-
@column_defaults = nil
-
@column_names = nil
-
@columns = nil
-
@columns_hash = nil
-
@column_types = nil
-
@content_columns = nil
-
@dynamic_methods_hash = nil
-
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
-
@relation = nil
-
@serialized_column_names = nil
-
@time_zone_column_names = nil
-
@cached_time_zone = nil
-
end
-
-
# This is a hook for use by modules that need to do extra stuff to
-
# attributes when they are initialized. (e.g. attribute
-
# serialization)
-
1
def initialize_attributes(attributes, options = {}) #:nodoc:
-
1063
attributes
-
end
-
-
1
private
-
-
# Guesses the table name, but does not decorate it with prefix and suffix information.
-
1
def undecorated_table_name(class_name = base_class.name)
-
4
table_name = class_name.to_s.demodulize.underscore
-
4
pluralize_table_names ? table_name.pluralize : table_name
-
end
-
-
# Computes and returns a table name according to default conventions.
-
1
def compute_table_name
-
4
base = base_class
-
4
if self == base
-
# Nested classes are prefixed with singular parent table name.
-
4
if parent < Base && !parent.abstract_class?
-
contained = parent.table_name
-
contained = contained.singularize if parent.pluralize_table_names
-
contained += '_'
-
end
-
4
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
-
else
-
# STI subclasses always use their superclass' table.
-
base.table_name
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
1
module NestedAttributes #:nodoc:
-
1
class TooManyRecords < ActiveRecordError
-
end
-
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :nested_attributes_options, instance_writer: false
-
1
self.nested_attributes_options = {}
-
end
-
-
# = Active Record Nested Attributes
-
#
-
# Nested attributes allow you to save attributes on associated records
-
# through the parent. By default nested attribute updating is turned off
-
# and you can enable it using the accepts_nested_attributes_for class
-
# method. When you enable nested attributes an attribute writer is
-
# defined on the model.
-
#
-
# The attribute writer is named after the association, which means that
-
# in the following example, two new methods are added to your model:
-
#
-
# <tt>author_attributes=(attributes)</tt> and
-
# <tt>pages_attributes=(attributes)</tt>.
-
#
-
# class Book < ActiveRecord::Base
-
# has_one :author
-
# has_many :pages
-
#
-
# accepts_nested_attributes_for :author, :pages
-
# end
-
#
-
# Note that the <tt>:autosave</tt> option is automatically enabled on every
-
# association that accepts_nested_attributes_for is used for.
-
#
-
# === One-to-one
-
#
-
# Consider a Member model that has one Avatar:
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar
-
# end
-
#
-
# Enabling nested attributes on a one-to-one association allows you to
-
# create the member and avatar in one go:
-
#
-
# params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
-
# member = Member.create(params[:member])
-
# member.avatar.id # => 2
-
# member.avatar.icon # => 'smiling'
-
#
-
# It also allows you to update the avatar through the member:
-
#
-
# params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
-
# member.update params[:member]
-
# member.avatar.icon # => 'sad'
-
#
-
# By default you will only be able to set and update attributes on the
-
# associated model. If you want to destroy the associated model through the
-
# attributes hash, you have to enable it first using the
-
# <tt>:allow_destroy</tt> option.
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar, allow_destroy: true
-
# end
-
#
-
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
-
# value that evaluates to +true+, you will destroy the associated model:
-
#
-
# member.avatar_attributes = { id: '2', _destroy: '1' }
-
# member.avatar.marked_for_destruction? # => true
-
# member.save
-
# member.reload.avatar # => nil
-
#
-
# Note that the model will _not_ be destroyed until the parent is saved.
-
#
-
# === One-to-many
-
#
-
# Consider a member that has a number of posts:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# You can now set or update attributes on the associated posts through
-
# an attribute hash for a member: include the key +:posts_attributes+
-
# with an array of hashes of post attributes as a value.
-
#
-
# For each hash that does _not_ have an <tt>id</tt> key a new record will
-
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
-
# that evaluates to +true+.
-
#
-
# params = { member: {
-
# name: 'joe', posts_attributes: [
-
# { title: 'Kari, the awesome Ruby documentation browser!' },
-
# { title: 'The egalitarian assumption of the modern citizen' },
-
# { title: '', _destroy: '1' } # this will be ignored
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# You may also set a :reject_if proc to silently ignore any new record
-
# hashes if they fail to pass your criteria. For example, the previous
-
# example could be rewritten as:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
-
# end
-
#
-
# params = { member: {
-
# name: 'joe', posts_attributes: [
-
# { title: 'Kari, the awesome Ruby documentation browser!' },
-
# { title: 'The egalitarian assumption of the modern citizen' },
-
# { title: '' } # this will be ignored because of the :reject_if proc
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# Alternatively, :reject_if also accepts a symbol for using methods:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: :new_record?
-
# end
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
-
#
-
# def reject_posts(attributed)
-
# attributed['title'].blank?
-
# end
-
# end
-
#
-
# If the hash contains an <tt>id</tt> key that matches an already
-
# associated record, the matching record will be modified:
-
#
-
# member.attributes = {
-
# name: 'Joe',
-
# posts_attributes: [
-
# { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
-
# { id: 2, title: '[UPDATED] other post' }
-
# ]
-
# }
-
#
-
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
-
# member.posts.second.title # => '[UPDATED] other post'
-
#
-
# By default the associated records are protected from being destroyed. If
-
# you want to destroy any of the associated records through the attributes
-
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
-
# option. This will allow you to also use the <tt>_destroy</tt> key to
-
# destroy existing records:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, allow_destroy: true
-
# end
-
#
-
# params = { member: {
-
# posts_attributes: [{ id: '2', _destroy: '1' }]
-
# }}
-
#
-
# member.attributes = params[:member]
-
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
-
# member.posts.length # => 2
-
# member.save
-
# member.reload.posts.length # => 1
-
#
-
# Nested attributes for an associated collection can also be passed in
-
# the form of a hash of hashes instead of an array of hashes:
-
#
-
# Member.create(name: 'joe',
-
# posts_attributes: { first: { title: 'Foo' },
-
# second: { title: 'Bar' } })
-
#
-
# has the same effect as
-
#
-
# Member.create(name: 'joe',
-
# posts_attributes: [ { title: 'Foo' },
-
# { title: 'Bar' } ])
-
#
-
# The keys of the hash which is the value for +:posts_attributes+ are
-
# ignored in this case.
-
# However, it is not allowed to use +'id'+ or +:id+ for one of
-
# such keys, otherwise the hash will be wrapped in an array and
-
# interpreted as an attribute hash for a single post.
-
#
-
# Passing attributes for an associated collection in the form of a hash
-
# of hashes can be used with hashes generated from HTTP/HTML parameters,
-
# where there maybe no natural way to submit an array of hashes.
-
#
-
# === Saving
-
#
-
# All changes to models, including the destruction of those marked for
-
# destruction, are saved and destroyed automatically and atomically when
-
# the parent model is saved. This happens inside the transaction initiated
-
# by the parents save method. See ActiveRecord::AutosaveAssociation.
-
#
-
# === Validating the presence of a parent model
-
#
-
# If you want to validate that a child record is associated with a parent
-
# record, you can use <tt>validates_presence_of</tt> and
-
# <tt>inverse_of</tt> as this example illustrates:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts, inverse_of: :member
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :member, inverse_of: :posts
-
# validates_presence_of :member
-
# end
-
#
-
# For one-to-one nested associations, if you build the new (in-memory)
-
# child object yourself before assignment, then this module will not
-
# overwrite it, e.g.:
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar
-
#
-
# def avatar
-
# super || build_avatar(width: 200)
-
# end
-
# end
-
#
-
# member = Member.new
-
# member.avatar_attributes = {icon: 'sad'}
-
# member.avatar.width # => 200
-
1
module ClassMethods
-
1
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
-
-
# Defines an attributes writer for the specified association(s).
-
#
-
# Supported options:
-
# [:allow_destroy]
-
# If true, destroys any members from the attributes hash with a
-
# <tt>_destroy</tt> key and a value that evaluates to +true+
-
# (eg. 1, '1', true, or 'true'). This option is off by default.
-
# [:reject_if]
-
# Allows you to specify a Proc or a Symbol pointing to a method
-
# that checks whether a record should be built for a certain attribute
-
# hash. The hash is passed to the supplied Proc or the method
-
# and it should return either +true+ or +false+. When no :reject_if
-
# is specified, a record will be built for all attribute hashes that
-
# do not have a <tt>_destroy</tt> value that evaluates to true.
-
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
-
# that will reject a record where all the attributes are blank excluding
-
# any value for _destroy.
-
# [:limit]
-
# Allows you to specify the maximum number of the associated records that
-
# can be processed with the nested attributes. Limit also can be specified as a
-
# Proc or a Symbol pointing to a method that should return number. If the size of the
-
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
-
# exception is raised. If omitted, any number associations can be processed.
-
# Note that the :limit option is only applicable to one-to-many associations.
-
# [:update_only]
-
# For a one-to-one association, this option allows you to specify how
-
# nested attributes are to be used when an associated record already
-
# exists. In general, an existing record may either be updated with the
-
# new set of attribute values or be replaced by a wholly new record
-
# containing those values. By default the :update_only option is +false+
-
# and the nested attributes are used to update the existing record only
-
# if they include the record's <tt>:id</tt> value. Otherwise a new
-
# record will be instantiated and used to replace the existing one.
-
# However if the :update_only option is +true+, the nested attributes
-
# are used to update the record's attributes always, regardless of
-
# whether the <tt>:id</tt> is present. The option is ignored for collection
-
# associations.
-
#
-
# Examples:
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, reject_if: :all_blank
-
# # creates avatar_attributes= and posts_attributes=
-
# accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
-
1
def accepts_nested_attributes_for(*attr_names)
-
options = { :allow_destroy => false, :update_only => false }
-
options.update(attr_names.extract_options!)
-
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
-
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
-
-
attr_names.each do |association_name|
-
if reflection = reflect_on_association(association_name)
-
reflection.options[:autosave] = true
-
add_autosave_association_callbacks(reflection)
-
-
nested_attributes_options = self.nested_attributes_options.dup
-
nested_attributes_options[association_name.to_sym] = options
-
self.nested_attributes_options = nested_attributes_options
-
-
type = (reflection.collection? ? :collection : :one_to_one)
-
generate_association_writer(association_name, type)
-
else
-
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
-
end
-
end
-
end
-
-
1
private
-
-
# Generates a writer method for this association. Serves as a point for
-
# accessing the objects in the association. For example, this method
-
# could generate the following:
-
#
-
# def pirate_attributes=(attributes)
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
-
# end
-
#
-
# This redirects the attempts to write objects in an association through
-
# the helper methods defined below. Makes it seem like the nested
-
# associations are just regular associations.
-
1
def generate_association_writer(association_name, type)
-
generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
-
if method_defined?(:#{association_name}_attributes=)
-
remove_method(:#{association_name}_attributes=)
-
end
-
def #{association_name}_attributes=(attributes)
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
-
end
-
eoruby
-
end
-
end
-
-
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
-
# used in conjunction with fields_for to build a form element for the
-
# destruction of this association.
-
#
-
# See ActionView::Helpers::FormHelper::fields_for for more info.
-
1
def _destroy
-
marked_for_destruction?
-
end
-
-
1
private
-
-
# Attribute hash keys that should not be assigned as normal attributes.
-
# These hash keys are nested attributes implementation details.
-
1
UNASSIGNABLE_KEYS = %w( id _destroy )
-
-
# Assigns the given attributes to the association.
-
#
-
# If an associated record does not yet exist, one will be instantiated. If
-
# an associated record already exists, the method's behavior depends on
-
# the value of the update_only option. If update_only is +false+ and the
-
# given attributes include an <tt>:id</tt> that matches the existing record's
-
# id, then the existing record will be modified. If no <tt>:id</tt> is provided
-
# it will be replaced with a new record. If update_only is +true+ the existing
-
# record will be modified regardless of whether an <tt>:id</tt> is provided.
-
#
-
# If the given attributes include a matching <tt>:id</tt> attribute, or
-
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
-
# then the existing record will be marked for destruction.
-
1
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
-
options = self.nested_attributes_options[association_name]
-
attributes = attributes.with_indifferent_access
-
existing_record = send(association_name)
-
-
if (options[:update_only] || !attributes['id'].blank?) && existing_record &&
-
(options[:update_only] || existing_record.id.to_s == attributes['id'].to_s)
-
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
-
-
elsif attributes['id'].present?
-
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
-
-
elsif !reject_new_record?(association_name, attributes)
-
assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
-
-
if existing_record && existing_record.new_record?
-
existing_record.assign_attributes(assignable_attributes)
-
association(association_name).initialize_attributes(existing_record)
-
else
-
method = "build_#{association_name}"
-
if respond_to?(method)
-
send(method, assignable_attributes)
-
else
-
raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
-
end
-
end
-
end
-
end
-
-
# Assigns the given attributes to the collection association.
-
#
-
# Hashes with an <tt>:id</tt> value matching an existing associated record
-
# will update that record. Hashes without an <tt>:id</tt> value will build
-
# a new record for the association. Hashes with a matching <tt>:id</tt>
-
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
-
# matched record for destruction.
-
#
-
# For example:
-
#
-
# assign_nested_attributes_for_collection_association(:people, {
-
# '1' => { id: '1', name: 'Peter' },
-
# '2' => { name: 'John' },
-
# '3' => { id: '2', _destroy: true }
-
# })
-
#
-
# Will update the name of the Person with ID 1, build a new associated
-
# person with the name 'John', and mark the associated Person with ID 2
-
# for destruction.
-
#
-
# Also accepts an Array of attribute hashes:
-
#
-
# assign_nested_attributes_for_collection_association(:people, [
-
# { id: '1', name: 'Peter' },
-
# { name: 'John' },
-
# { id: '2', _destroy: true }
-
# ])
-
1
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
-
options = self.nested_attributes_options[association_name]
-
-
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
-
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
-
end
-
-
check_record_limit!(options[:limit], attributes_collection)
-
-
if attributes_collection.is_a? Hash
-
keys = attributes_collection.keys
-
attributes_collection = if keys.include?('id') || keys.include?(:id)
-
[attributes_collection]
-
else
-
attributes_collection.values
-
end
-
end
-
-
association = association(association_name)
-
-
existing_records = if association.loaded?
-
association.target
-
else
-
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
-
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
-
end
-
-
attributes_collection.each do |attributes|
-
attributes = attributes.with_indifferent_access
-
-
if attributes['id'].blank?
-
unless reject_new_record?(association_name, attributes)
-
association.build(attributes.except(*UNASSIGNABLE_KEYS))
-
end
-
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
-
unless association.loaded? || call_reject_if(association_name, attributes)
-
# Make sure we are operating on the actual object which is in the association's
-
# proxy_target array (either by finding it, or adding it if not found)
-
target_record = association.target.detect { |record| record == existing_record }
-
-
if target_record
-
existing_record = target_record
-
else
-
association.add_to_target(existing_record)
-
end
-
end
-
-
if !call_reject_if(association_name, attributes)
-
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
-
end
-
else
-
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
-
end
-
end
-
end
-
-
# Takes in a limit and checks if the attributes_collection has too many
-
# records. The method will take limits in the form of symbols, procs, and
-
# number-like objects (anything that can be compared with an integer).
-
#
-
# Will raise an TooManyRecords error if the attributes_collection is
-
# larger than the limit.
-
1
def check_record_limit!(limit, attributes_collection)
-
if limit
-
limit = case limit
-
when Symbol
-
send(limit)
-
when Proc
-
limit.call
-
else
-
limit
-
end
-
-
if limit && attributes_collection.size > limit
-
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
-
end
-
end
-
end
-
-
# Updates a record with the +attributes+ or marks it for destruction if
-
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
-
1
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
-
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
-
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
-
end
-
-
# Determines if a hash contains a truthy _destroy key.
-
1
def has_destroy_flag?(hash)
-
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
-
end
-
-
# Determines if a new record should be build by checking for
-
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
-
# association and evaluates to +true+.
-
1
def reject_new_record?(association_name, attributes)
-
has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
-
end
-
-
# Determines if a record with the particular +attributes+ should be
-
# rejected by calling the reject_if Symbol or Proc (if defined).
-
# The reject_if option is defined by +accepts_nested_attributes_for+.
-
#
-
# Returns false if there is a +destroy_flag+ on the attributes.
-
1
def call_reject_if(association_name, attributes)
-
return false if has_destroy_flag?(attributes)
-
case callback = self.nested_attributes_options[association_name][:reject_if]
-
when Symbol
-
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
-
when Proc
-
callback.call(attributes)
-
end
-
end
-
-
1
def raise_nested_attributes_record_not_found!(association_name, record_id)
-
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Persistence
-
1
module Persistence
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
-
# The resulting object is returned whether the object was saved successfully to the database or not.
-
#
-
# The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
-
# attributes on the objects that are to be created.
-
#
-
# ==== Examples
-
# # Create a single new object
-
# User.create(first_name: 'Jamie')
-
#
-
# # Create an Array of new objects
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
-
#
-
# # Create a single object and pass it into a block to set other attributes.
-
# User.create(first_name: 'Jamie') do |u|
-
# u.is_admin = false
-
# end
-
#
-
# # Creating an Array of new objects using a block, where the block is executed for each object:
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
-
# u.is_admin = false
-
# end
-
1
def create(attributes = nil, &block)
-
if attributes.is_a?(Array)
-
attributes.collect { |attr| create(attr, &block) }
-
else
-
object = new(attributes, &block)
-
object.save
-
object
-
end
-
end
-
-
# Given an attributes hash, +instantiate+ returns a new instance of
-
# the appropriate class.
-
#
-
# For example, +Post.all+ may return Comments, Messages, and Emails
-
# by storing the record's subclass in a +type+ attribute. By calling
-
# +instantiate+ instead of +new+, finder methods ensure they get new
-
# instances of the appropriate class for each record.
-
#
-
# See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
-
# how this "single-table" inheritance mapping is implemented.
-
1
def instantiate(record, column_types = {})
-
608
klass = discriminate_class_for_record(record)
-
608
column_types = klass.decorate_columns(column_types.dup)
-
608
klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
-
end
-
-
1
private
-
# Called by +instantiate+ to decide which class to use for a new
-
# record instance.
-
#
-
# See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
-
# the single-table inheritance discriminator.
-
1
def discriminate_class_for_record(record)
-
608
self
-
end
-
end
-
-
# Returns true if this object hasn't been saved yet -- that is, a record
-
# for the object doesn't exist in the database yet; otherwise, returns false.
-
1
def new_record?
-
2994
sync_with_transaction_state
-
2994
@new_record
-
end
-
-
# Returns true if this object has been destroyed, otherwise returns false.
-
1
def destroyed?
-
683
sync_with_transaction_state
-
683
@destroyed
-
end
-
-
# Returns true if the record is persisted, i.e. it's not a new record and it was
-
# not destroyed, otherwise returns false.
-
1
def persisted?
-
1267
!(new_record? || destroyed?)
-
end
-
-
# Saves the model.
-
#
-
# If the model is new a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# By default, save always run validations. If any of them fail the action
-
# is cancelled and +save+ returns +false+. However, if you supply
-
# validate: false, validations are bypassed altogether. See
-
# ActiveRecord::Validations for more information.
-
#
-
# There's a series of callbacks associated with +save+. If any of the
-
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
-
# +save+ returns +false+. See ActiveRecord::Callbacks for further
-
# details.
-
#
-
# Attributes marked as readonly are silently ignored if the record is
-
# being updated.
-
1
def save(*)
-
124
create_or_update
-
rescue ActiveRecord::RecordInvalid
-
false
-
end
-
-
# Saves the model.
-
#
-
# If the model is new a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# With <tt>save!</tt> validations always run. If any of them fail
-
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
-
# for more information.
-
#
-
# There's a series of callbacks associated with <tt>save!</tt>. If any of
-
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
-
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
-
# ActiveRecord::Callbacks for further details.
-
#
-
# Attributes marked as readonly are silently ignored if the record is
-
# being updated.
-
1
def save!(*)
-
263
create_or_update || raise(RecordNotSaved)
-
end
-
-
# Deletes the record in the database and freezes this instance to
-
# reflect that no changes should be made (since they can't be
-
# persisted). Returns the frozen instance.
-
#
-
# The row is simply removed with an SQL +DELETE+ statement on the
-
# record's primary key, and no callbacks are executed.
-
#
-
# To enforce the object's +before_destroy+ and +after_destroy+
-
# callbacks or any <tt>:dependent</tt> association
-
# options, use <tt>#destroy</tt>.
-
1
def delete
-
self.class.delete(id) if persisted?
-
@destroyed = true
-
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with <tt>destroy</tt>. If
-
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
-
# and <tt>destroy</tt> returns +false+. See
-
# ActiveRecord::Callbacks for further details.
-
1
def destroy
-
12
raise ReadOnlyRecord if readonly?
-
12
destroy_associations
-
12
destroy_row if persisted?
-
12
@destroyed = true
-
12
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with <tt>destroy!</tt>. If
-
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
-
# and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
-
# ActiveRecord::Callbacks for further details.
-
1
def destroy!
-
destroy || raise(ActiveRecord::RecordNotDestroyed)
-
end
-
-
# Returns an instance of the specified +klass+ with the attributes of the
-
# current record. This is mostly useful in relation to single-table
-
# inheritance structures where you want a subclass to appear as the
-
# superclass. This can be used along with record identification in
-
# Action Pack to allow, say, <tt>Client < Company</tt> to do something
-
# like render <tt>partial: @client.becomes(Company)</tt> to render that
-
# instance using the companies/company partial instead of clients/client.
-
#
-
# Note: The new instance will share a link to the same attributes as the original class.
-
# So any change to the attributes in either instance will affect the other.
-
1
def becomes(klass)
-
became = klass.new
-
became.instance_variable_set("@attributes", @attributes)
-
became.instance_variable_set("@attributes_cache", @attributes_cache)
-
became.instance_variable_set("@new_record", new_record?)
-
became.instance_variable_set("@destroyed", destroyed?)
-
became.instance_variable_set("@errors", errors)
-
became
-
end
-
-
# Wrapper around +becomes+ that also changes the instance's sti column value.
-
# This is especially useful if you want to persist the changed class in your
-
# database.
-
#
-
# Note: The old instance's sti column value will be changed too, as both objects
-
# share the same set of attributes.
-
1
def becomes!(klass)
-
became = becomes(klass)
-
sti_type = nil
-
if !klass.descends_from_active_record?
-
sti_type = klass.sti_name
-
end
-
became.public_send("#{klass.inheritance_column}=", sti_type)
-
became
-
end
-
-
# Updates a single attribute and saves the record.
-
# This is especially useful for boolean flags on existing records. Also note that
-
#
-
# * Validation is skipped.
-
# * Callbacks are invoked.
-
# * updated_at/updated_on column is updated if that column is available.
-
# * Updates all the attributes that are dirty in this object.
-
#
-
# This method raises an +ActiveRecord::ActiveRecordError+ if the
-
# attribute is marked as readonly.
-
1
def update_attribute(name, value)
-
69
name = name.to_s
-
69
verify_readonly_attribute(name)
-
69
send("#{name}=", value)
-
69
save(validate: false)
-
end
-
-
# Updates the attributes of the model from the passed-in hash and saves the
-
# record, all wrapped in a transaction. If the object is invalid, the saving
-
# will fail and false will be returned.
-
1
def update(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
6
with_transaction_returning_status do
-
6
assign_attributes(attributes)
-
6
save
-
end
-
end
-
-
1
alias update_attributes update
-
-
# Updates its receiver just like +update+ but calls <tt>save!</tt> instead
-
# of +save+, so an exception is raised if the record is invalid.
-
1
def update!(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
with_transaction_returning_status do
-
assign_attributes(attributes)
-
save!
-
end
-
end
-
-
1
alias update_attributes! update!
-
-
# Equivalent to <code>update_columns(name => value)</code>.
-
1
def update_column(name, value)
-
update_columns(name => value)
-
end
-
-
# Updates the attributes directly in the database issuing an UPDATE SQL
-
# statement and sets them in the receiver:
-
#
-
# user.update_columns(last_request_at: Time.current)
-
#
-
# This is the fastest way to update attributes because it goes straight to
-
# the database, but take into account that in consequence the regular update
-
# procedures are totally bypassed. In particular:
-
#
-
# * Validations are skipped.
-
# * Callbacks are skipped.
-
# * +updated_at+/+updated_on+ are not updated.
-
#
-
# This method raises an +ActiveRecord::ActiveRecordError+ when called on new
-
# objects, or when at least one of the attributes is marked as readonly.
-
1
def update_columns(attributes)
-
raise ActiveRecordError, "can not update on a new record object" unless persisted?
-
-
attributes.each_key do |key|
-
verify_readonly_attribute(key.to_s)
-
end
-
-
updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
-
-
attributes.each do |k, v|
-
raw_write_attribute(k, v)
-
end
-
-
updated_count == 1
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
-
# The increment is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
1
def increment(attribute, by = 1)
-
self[attribute] ||= 0
-
self[attribute] += by
-
self
-
end
-
-
# Wrapper around +increment+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def increment!(attribute, by = 1)
-
increment(attribute, by).update_attribute(attribute, self[attribute])
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
-
# The decrement is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
1
def decrement(attribute, by = 1)
-
self[attribute] ||= 0
-
self[attribute] -= by
-
self
-
end
-
-
# Wrapper around +decrement+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def decrement!(attribute, by = 1)
-
decrement(attribute, by).update_attribute(attribute, self[attribute])
-
end
-
-
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
-
# if the predicate returns +true+ the attribute will become +false+. This
-
# method toggles directly the underlying value without calling any setter.
-
# Returns +self+.
-
1
def toggle(attribute)
-
1
self[attribute] = !send("#{attribute}?")
-
1
self
-
end
-
-
# Wrapper around +toggle+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def toggle!(attribute)
-
1
toggle(attribute).update_attribute(attribute, self[attribute])
-
end
-
-
# Reloads the record from the database.
-
#
-
# This method finds record by its primary key (which could be assigned manually) and
-
# modifies the receiver in-place:
-
#
-
# account = Account.new
-
# # => #<Account id: nil, email: nil>
-
# account.id = 1
-
# account.reload
-
# # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
-
# # => #<Account id: 1, email: 'account@example.com'>
-
#
-
# Attributes are updated, and caches busted, in particular the associations cache.
-
#
-
# If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
-
# is raised. Otherwise, in addition to the in-place modification the method
-
# returns +self+ for convenience.
-
#
-
# The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
-
#
-
# reload(lock: true) # reload with pessimistic locking
-
#
-
# Reloading is commonly used in test suites to test something is actually
-
# written to the database, or when some action modifies the corresponding
-
# row in the database but not the object in memory:
-
#
-
# assert account.deposit!(25)
-
# assert_equal 25, account.credit # check it is updated in memory
-
# assert_equal 25, account.reload.credit # check it is also persisted
-
#
-
# Another commom use case is optimistic locking handling:
-
#
-
# def with_optimistic_retry
-
# begin
-
# yield
-
# rescue ActiveRecord::StaleObjectError
-
# begin
-
# # Reload lock_version in particular.
-
# reload
-
# rescue ActiveRecord::RecordNotFound
-
# # If the record is gone there is nothing to do.
-
# else
-
# retry
-
# end
-
# end
-
# end
-
#
-
1
def reload(options = nil)
-
4
clear_aggregation_cache
-
4
clear_association_cache
-
-
4
fresh_object =
-
if options && options[:lock]
-
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
-
else
-
8
self.class.unscoped { self.class.find(id) }
-
end
-
-
4
@attributes.update(fresh_object.instance_variable_get('@attributes'))
-
-
4
@column_types = self.class.column_types
-
4
@column_types_override = fresh_object.instance_variable_get('@column_types_override')
-
4
@attributes_cache = {}
-
4
self
-
end
-
-
# Saves the record with the updated_at/on attributes set to the current time.
-
# Please note that no validation is performed and only the +after_touch+
-
# callback is executed.
-
# If an attribute name is passed, that attribute is updated along with
-
# updated_at/on attributes.
-
#
-
# product.touch # updates updated_at/on
-
# product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
-
#
-
# If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
-
#
-
# class Brake < ActiveRecord::Base
-
# belongs_to :car, touch: true
-
# end
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :corporation, touch: true
-
# end
-
#
-
# # triggers @brake.car.touch and @brake.car.corporation.touch
-
# @brake.touch
-
#
-
# Note that +touch+ must be used on a persisted object, or else an
-
# ActiveRecordError will be thrown. For example:
-
#
-
# ball = Ball.new
-
# ball.touch(:updated_at) # => raises ActiveRecordError
-
#
-
1
def touch(name = nil)
-
raise ActiveRecordError, "can not touch on a new record object" unless persisted?
-
-
attributes = timestamp_attributes_for_update_in_model
-
attributes << name if name
-
-
unless attributes.empty?
-
current_time = current_time_from_proper_timezone
-
changes = {}
-
-
attributes.each do |column|
-
column = column.to_s
-
changes[column] = write_attribute(column, current_time)
-
end
-
-
changes[self.class.locking_column] = increment_lock if locking_enabled?
-
-
@changed_attributes.except!(*changes.keys)
-
primary_key = self.class.primary_key
-
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
-
end
-
end
-
-
1
private
-
-
# A hook to be overridden by association modules.
-
1
def destroy_associations
-
end
-
-
1
def destroy_row
-
12
relation_for_destroy.delete_all
-
end
-
-
1
def relation_for_destroy
-
12
pk = self.class.primary_key
-
12
column = self.class.columns_hash[pk]
-
12
substitute = self.class.connection.substitute_at(column, 0)
-
-
12
relation = self.class.unscoped.where(
-
self.class.arel_table[pk].eq(substitute))
-
-
12
relation.bind_values = [[column, id]]
-
12
relation
-
end
-
-
1
def create_or_update
-
387
raise ReadOnlyRecord if readonly?
-
387
result = new_record? ? create_record : update_record
-
387
result != false
-
end
-
-
# Updates the associated record with values matching those of the instance attributes.
-
# Returns the number of affected rows.
-
1
def update_record(attribute_names = @attributes.keys)
-
96
attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
-
96
if attributes_with_values.empty?
-
22
0
-
else
-
74
klass = self.class
-
74
column_hash = klass.connection.schema_cache.columns_hash klass.table_name
-
74
db_columns_with_values = attributes_with_values.map { |attr,value|
-
158
real_column = column_hash[attr.name]
-
158
[real_column, value]
-
}
-
74
bind_attrs = attributes_with_values.dup
-
74
bind_attrs.keys.each_with_index do |column, i|
-
158
real_column = db_columns_with_values[i].first
-
158
bind_attrs[column] = klass.connection.substitute_at(real_column, i)
-
end
-
74
stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was || id)).arel.compile_update(bind_attrs)
-
74
klass.connection.update stmt, 'SQL', db_columns_with_values
-
end
-
end
-
-
# Creates a record with values matching those of the instance attributes
-
# and returns its id.
-
1
def create_record(attribute_names = @attributes.keys)
-
291
attributes_values = arel_attributes_with_values_for_create(attribute_names)
-
-
291
new_id = self.class.unscoped.insert attributes_values
-
291
self.id ||= new_id if self.class.primary_key
-
-
291
@new_record = false
-
291
id
-
end
-
-
1
def verify_readonly_attribute(name)
-
69
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Querying
-
1
delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all
-
1
delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :all
-
1
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :to => :all
-
1
delegate :find_by, :find_by!, :to => :all
-
1
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :all
-
1
delegate :find_each, :find_in_batches, :to => :all
-
1
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
-
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
-
:having, :create_with, :uniq, :distinct, :references, :none, :unscope, :to => :all
-
1
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
-
-
# Executes a custom SQL query against your database and returns all the results. The results will
-
# be returned as an array with columns requested encapsulated as attributes of the model you call
-
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
-
# a Product object with the attributes you specified in the SQL query.
-
#
-
# If you call a complicated SQL query which spans multiple tables the columns specified by the
-
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
-
# table.
-
#
-
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
-
# no database agnostic conversions performed. This should be a last resort because using, for example,
-
# MySQL specific terms will lock you to using that particular database engine or require you to
-
# change your call if you switch engines.
-
#
-
# # A simple SQL query spanning multiple tables
-
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
-
# # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
-
#
-
# # You can use the same string replacement techniques as you can with ActiveRecord#find
-
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
-
# # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
-
1
def find_by_sql(sql, binds = [])
-
668
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
-
668
column_types = {}
-
-
668
if result_set.respond_to? :column_types
-
668
column_types = result_set.column_types
-
else
-
ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
-
end
-
-
1276
result_set.map { |record| instantiate(record, column_types) }
-
end
-
-
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
-
# The use of this method should be restricted to complicated SQL queries that can't be executed
-
# using the ActiveRecord::Calculations class methods. Look into those before using this.
-
#
-
# ==== Parameters
-
#
-
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
-
#
-
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
-
1
def count_by_sql(sql)
-
sql = sanitize_conditions(sql)
-
connection.select_value(sql, "#{name} Count").to_i
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module ReadonlyAttributes
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_attr_readonly, instance_accessor: false
-
1
self._attr_readonly = []
-
end
-
-
1
module ClassMethods
-
# Attributes listed as readonly will be used to create a new record but update operations will
-
# ignore these fields.
-
1
def attr_readonly(*attributes)
-
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
-
end
-
-
# Returns an array of all the attributes that have been specified as readonly.
-
1
def readonly_attributes
-
227
self._attr_readonly
-
end
-
end
-
-
1
def _attr_readonly
-
message = "Instance level _attr_readonly method is deprecated, please use class level method."
-
ActiveSupport::Deprecation.warn message
-
defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Reflection
-
1
module Reflection # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :reflections
-
1
self.reflections = {}
-
end
-
-
# Reflection enables to interrogate Active Record classes and objects
-
# about their associations and aggregations. This information can,
-
# for example, be used in a form builder that takes an Active Record object
-
# and creates input fields for all of the attributes depending on their type
-
# and displays the associations to other objects.
-
#
-
# MacroReflection class has info for AggregateReflection and AssociationReflection
-
# classes.
-
1
module ClassMethods
-
1
def create_reflection(macro, name, scope, options, active_record)
-
14
case macro
-
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
-
14
klass = options[:through] ? ThroughReflection : AssociationReflection
-
14
reflection = klass.new(macro, name, scope, options, active_record)
-
when :composed_of
-
reflection = AggregateReflection.new(macro, name, scope, options, active_record)
-
end
-
-
14
self.reflections = self.reflections.merge(name => reflection)
-
14
reflection
-
end
-
-
# Returns an array of AggregateReflection objects for all the aggregations in the class.
-
1
def reflect_on_all_aggregations
-
reflections.values.grep(AggregateReflection)
-
end
-
-
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
-
#
-
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
-
#
-
1
def reflect_on_aggregation(aggregation)
-
445
reflection = reflections[aggregation]
-
445
reflection if reflection.is_a?(AggregateReflection)
-
end
-
-
# Returns an array of AssociationReflection objects for all the
-
# associations in the class. If you only want to reflect on a certain
-
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
-
# <tt>:belongs_to</tt>) as the first parameter.
-
#
-
# Example:
-
#
-
# Account.reflect_on_all_associations # returns an array of all associations
-
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
-
#
-
1
def reflect_on_all_associations(macro = nil)
-
1
association_reflections = reflections.values.grep(AssociationReflection)
-
3
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
-
end
-
-
# Returns the AssociationReflection object for the +association+ (use the symbol).
-
#
-
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
-
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
-
#
-
1
def reflect_on_association(association)
-
2540
reflection = reflections[association]
-
2540
reflection if reflection.is_a?(AssociationReflection)
-
end
-
-
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
-
1
def reflect_on_all_autosave_associations
-
reflections.values.select { |reflection| reflection.options[:autosave] }
-
end
-
end
-
-
# Base class for AggregateReflection and AssociationReflection. Objects of
-
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
-
#
-
# MacroReflection
-
# AggregateReflection
-
# AssociationReflection
-
# ThroughReflection
-
1
class MacroReflection
-
# Returns the name of the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
-
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
-
1
attr_reader :name
-
-
# Returns the macro type.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
-
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
-
1
attr_reader :macro
-
-
1
attr_reader :scope
-
-
# Returns the hash of options used for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
-
# <tt>has_many :clients</tt> returns +{}+
-
1
attr_reader :options
-
-
1
attr_reader :active_record
-
-
1
attr_reader :plural_name # :nodoc:
-
-
1
def initialize(macro, name, scope, options, active_record)
-
14
@macro = macro
-
14
@name = name
-
14
@scope = scope
-
14
@options = options
-
14
@active_record = active_record
-
14
@plural_name = active_record.pluralize_table_names ?
-
name.to_s.pluralize : name.to_s
-
end
-
-
# Returns the class for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
-
# <tt>has_many :clients</tt> returns the Client class
-
1
def klass
-
@klass ||= class_name.constantize
-
end
-
-
# Returns the class name for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
-
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
-
1
def class_name
-
16
@class_name ||= (options[:class_name] || derive_class_name).to_s
-
end
-
-
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
-
# and +other_aggregation+ has an options hash assigned to it.
-
1
def ==(other_aggregation)
-
super ||
-
other_aggregation.kind_of?(self.class) &&
-
name == other_aggregation.name &&
-
other_aggregation.options &&
-
2163
active_record == other_aggregation.active_record
-
end
-
-
1
private
-
1
def derive_class_name
-
name.to_s.camelize
-
end
-
end
-
-
-
# Holds all the meta-data about an aggregation as it was specified in the
-
# Active Record class.
-
1
class AggregateReflection < MacroReflection #:nodoc:
-
1
def mapping
-
mapping = options[:mapping] || [name, name]
-
mapping.first.is_a?(Array) ? mapping : [mapping]
-
end
-
end
-
-
# Holds all the meta-data about an association as it was specified in the
-
# Active Record class.
-
1
class AssociationReflection < MacroReflection #:nodoc:
-
# Returns the target association's class.
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :books
-
# end
-
#
-
# Author.reflect_on_association(:books).klass
-
# # => Book
-
#
-
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
-
# a new association object. Use +build_association+ or +create_association+
-
# instead. This allows plugins to hook into association object creation.
-
1
def klass
-
8560
@klass ||= active_record.send(:compute_type, class_name)
-
end
-
-
1
def initialize(*args)
-
14
super
-
14
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
-
end
-
-
# Returns a new, unsaved instance of the associated class. +attributes+ will
-
# be passed to the class's constructor.
-
1
def build_association(attributes, &block)
-
141
klass.new(attributes, &block)
-
end
-
-
1
def table_name
-
225
@table_name ||= klass.table_name
-
end
-
-
1
def quoted_table_name
-
@quoted_table_name ||= klass.quoted_table_name
-
end
-
-
1
def join_table
-
@join_table ||= options[:join_table] || derive_join_table
-
end
-
-
1
def foreign_key
-
2226
@foreign_key ||= options[:foreign_key] || derive_foreign_key
-
end
-
-
1
def foreign_type
-
@foreign_type ||= options[:foreign_type] || "#{name}_type"
-
end
-
-
1
def type
-
862
@type ||= options[:as] && "#{options[:as]}_type"
-
end
-
-
1
def primary_key_column
-
@primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
-
end
-
-
1
def association_foreign_key
-
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
-
end
-
-
# klass option is necessary to support loading polymorphic associations
-
1
def association_primary_key(klass = nil)
-
117
options[:primary_key] || primary_key(klass || self.klass)
-
end
-
-
1
def active_record_primary_key
-
462
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
-
end
-
-
1
def counter_cache_column
-
46
if options[:counter_cache] == true
-
"#{active_record.name.demodulize.underscore.pluralize}_count"
-
46
elsif options[:counter_cache]
-
options[:counter_cache].to_s
-
end
-
end
-
-
1
def columns(tbl_name)
-
@columns ||= klass.connection.columns(tbl_name)
-
end
-
-
1
def reset_column_information
-
@columns = nil
-
end
-
-
1
def check_validity!
-
315
check_validity_of_inverse!
-
-
315
if has_and_belongs_to_many? && association_foreign_key == foreign_key
-
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
-
end
-
end
-
-
1
def check_validity_of_inverse!
-
540
unless options[:polymorphic]
-
540
if has_inverse? && inverse_of.nil?
-
raise InverseOfAssociationNotFoundError.new(self)
-
end
-
end
-
end
-
-
1
def through_reflection
-
nil
-
end
-
-
1
def source_reflection
-
nil
-
end
-
-
# A chain of reflections from this one back to the owner. For more see the explanation in
-
# ThroughReflection.
-
1
def chain
-
817
[self]
-
end
-
-
1
def nested?
-
false
-
end
-
-
# An array of arrays of scopes. Each item in the outside array corresponds to a reflection
-
# in the #chain.
-
1
def scope_chain
-
275
scope ? [[scope]] : [[]]
-
end
-
-
1
alias :source_macro :macro
-
-
1
def has_inverse?
-
1110
@options[:inverse_of]
-
end
-
-
1
def inverse_of
-
570
if has_inverse?
-
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
-
end
-
end
-
-
1
def polymorphic_inverse_of(associated_class)
-
if has_inverse?
-
if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
-
inverse_relationship
-
else
-
raise InverseOfAssociationNotFoundError.new(self, associated_class)
-
end
-
end
-
end
-
-
# Returns whether or not this association reflection is for a collection
-
# association. Returns +true+ if the +macro+ is either +has_many+ or
-
# +has_and_belongs_to_many+, +false+ otherwise.
-
1
def collection?
-
17
@collection
-
end
-
-
# Returns whether or not the association should be validated as part of
-
# the parent's validation.
-
#
-
# Unless you explicitly disable validation with
-
# <tt>validate: false</tt>, validation will take place when:
-
#
-
# * you explicitly enable validation; <tt>validate: true</tt>
-
# * you use autosave; <tt>autosave: true</tt>
-
# * the association is a +has_many+ association
-
1
def validate?
-
14
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
-
end
-
-
# Returns +true+ if +self+ is a +belongs_to+ reflection.
-
1
def belongs_to?
-
4
macro == :belongs_to
-
end
-
-
1
def has_and_belongs_to_many?
-
315
macro == :has_and_belongs_to_many
-
end
-
-
1
def association_class
-
540
case macro
-
when :belongs_to
-
117
if options[:polymorphic]
-
Associations::BelongsToPolymorphicAssociation
-
else
-
117
Associations::BelongsToAssociation
-
end
-
when :has_and_belongs_to_many
-
Associations::HasAndBelongsToManyAssociation
-
when :has_many
-
423
if options[:through]
-
225
Associations::HasManyThroughAssociation
-
else
-
198
Associations::HasManyAssociation
-
end
-
when :has_one
-
if options[:through]
-
Associations::HasOneThroughAssociation
-
else
-
Associations::HasOneAssociation
-
end
-
end
-
end
-
-
1
def polymorphic?
-
options.key? :polymorphic
-
end
-
-
1
private
-
1
def derive_class_name
-
3
class_name = name.to_s.camelize
-
3
class_name = class_name.singularize if collection?
-
3
class_name
-
end
-
-
1
def derive_foreign_key
-
4
if belongs_to?
-
3
"#{name}_id"
-
1
elsif options[:as]
-
"#{options[:as]}_id"
-
else
-
1
active_record.name.foreign_key
-
end
-
end
-
-
1
def derive_join_table
-
[active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
-
end
-
-
1
def primary_key(klass)
-
348
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
-
end
-
end
-
-
# Holds all the meta-data about a :through association as it was specified
-
# in the Active Record class.
-
1
class ThroughReflection < AssociationReflection #:nodoc:
-
1
delegate :foreign_key, :foreign_type, :association_foreign_key,
-
:active_record_primary_key, :type, :to => :source_reflection
-
-
# Gets the source of the through reflection. It checks both a singularized
-
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# class Tagging < ActiveRecord::Base
-
# belongs_to :post
-
# belongs_to :tag
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
#
-
# taggings_reflection = tags_reflection.source_reflection
-
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
-
#
-
1
def source_reflection
-
1808
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
-
end
-
-
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
-
# of a HasManyThrough or HasOneThrough association.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# taggings_reflection = tags_reflection.through_reflection
-
#
-
1
def through_reflection
-
458
@through_reflection ||= active_record.reflect_on_association(options[:through])
-
end
-
-
# Returns an array of reflections which are involved in this association. Each item in the
-
# array corresponds to a table which will be part of the query for this association.
-
#
-
# The chain is built by recursively calling #chain on the source reflection and the through
-
# reflection. The base case for the recursion is a normal association, which just returns
-
# [self] as its #chain.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.chain
-
# # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
-
# <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
-
#
-
1
def chain
-
@chain ||= begin
-
2
chain = source_reflection.chain + through_reflection.chain
-
2
chain[0] = self # Use self so we don't lose the information from :source_type
-
2
chain
-
1367
end
-
end
-
-
# Consider the following example:
-
#
-
# class Person
-
# has_many :articles
-
# has_many :comment_tags, through: :articles
-
# end
-
#
-
# class Article
-
# has_many :comments
-
# has_many :comment_tags, through: :comments, source: :tags
-
# end
-
#
-
# class Comment
-
# has_many :tags
-
# end
-
#
-
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
-
# but only Comment.tags will be represented in the #chain. So this method creates an array
-
# of scopes corresponding to the chain.
-
1
def scope_chain
-
@scope_chain ||= begin
-
2
scope_chain = source_reflection.scope_chain.map(&:dup)
-
-
# Add to it the scope from this reflection (if any)
-
2
scope_chain.first << scope if scope
-
-
2
through_scope_chain = through_reflection.scope_chain.map(&:dup)
-
-
2
if options[:source_type]
-
through_scope_chain.first <<
-
through_reflection.klass.where(foreign_type => options[:source_type])
-
end
-
-
# Recursively fill out the rest of the array from the through reflection
-
2
scope_chain + through_scope_chain
-
450
end
-
end
-
-
# The macro used by the source association
-
1
def source_macro
-
675
source_reflection.source_macro
-
end
-
-
# A through association is nested if there would be more than one join table
-
1
def nested?
-
chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
-
end
-
-
# We want to use the klass from this reflection, rather than just delegate straight to
-
# the source_reflection, because the source_reflection may be polymorphic. We still
-
# need to respect the source_reflection's :primary_key option, though.
-
1
def association_primary_key(klass = nil)
-
# Get the "actual" source reflection if the immediate source reflection has a
-
# source reflection itself
-
225
source_reflection = self.source_reflection
-
225
while source_reflection.source_reflection
-
source_reflection = source_reflection.source_reflection
-
end
-
-
225
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
-
end
-
-
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.source_reflection_names
-
# # => [:tag, :tags]
-
#
-
1
def source_reflection_names
-
4
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
-
end
-
-
1
def source_options
-
source_reflection.options
-
end
-
-
1
def through_options
-
through_reflection.options
-
end
-
-
1
def check_validity!
-
225
if through_reflection.nil?
-
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
-
end
-
-
225
if through_reflection.options[:polymorphic]
-
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
-
end
-
-
225
if source_reflection.nil?
-
raise HasManyThroughSourceAssociationNotFoundError.new(self)
-
end
-
-
225
if options[:source_type] && source_reflection.options[:polymorphic].nil?
-
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
-
end
-
-
225
if source_reflection.options[:polymorphic] && options[:source_type].nil?
-
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
-
end
-
-
225
if macro == :has_one && through_reflection.collection?
-
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
-
end
-
-
225
check_validity_of_inverse!
-
end
-
-
1
private
-
1
def derive_class_name
-
# get the class_name of the belongs_to association of the through reflection
-
2
options[:source_type] || source_reflection.class_name
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class PredicateBuilder # :nodoc:
-
1
def self.build_from_hash(klass, attributes, default_table)
-
445
queries = []
-
-
445
attributes.each do |column, value|
-
445
table = default_table
-
-
445
if value.is_a?(Hash)
-
if value.empty?
-
queries << '1=0'
-
else
-
table = Arel::Table.new(column, default_table.engine)
-
association = klass.reflect_on_association(column.to_sym)
-
-
value.each do |k, v|
-
queries.concat expand(association && association.klass, table, k, v)
-
end
-
end
-
else
-
445
column = column.to_s
-
-
445
if column.include?('.')
-
table_name, column = column.split('.', 2)
-
table = Arel::Table.new(table_name, default_table.engine)
-
end
-
-
445
queries.concat expand(klass, table, column, value)
-
end
-
end
-
-
445
queries
-
end
-
-
1
def self.expand(klass, table, column, value)
-
445
queries = []
-
-
# Find the foreign key when using queries such as:
-
# Post.where(author: author)
-
#
-
# For polymorphic relationships, find the foreign key and type:
-
# PriceEstimate.where(estimate_of: treasure)
-
445
if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym)
-
if reflection.polymorphic?
-
queries << build(table[reflection.foreign_type], value.class.base_class)
-
end
-
-
column = reflection.foreign_key
-
end
-
-
445
queries << build(table[column], value)
-
445
queries
-
end
-
-
1
def self.references(attributes)
-
attributes.map do |key, value|
-
445
if value.is_a?(Hash)
-
key
-
else
-
445
key = key.to_s
-
445
key.split('.').first if key.include?('.')
-
end
-
445
end.compact
-
end
-
-
1
private
-
1
def self.build(attribute, value)
-
445
case value
-
when Array
-
values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x}
-
ranges, values = values.partition {|v| v.is_a?(Range)}
-
-
values_predicate = if values.include?(nil)
-
values = values.compact
-
-
case values.length
-
when 0
-
attribute.eq(nil)
-
when 1
-
attribute.eq(values.first).or(attribute.eq(nil))
-
else
-
attribute.in(values).or(attribute.eq(nil))
-
end
-
else
-
attribute.in(values)
-
end
-
-
array_predicates = ranges.map { |range| attribute.in(range) }
-
array_predicates << values_predicate
-
array_predicates.inject { |composite, predicate| composite.or(predicate) }
-
when ActiveRecord::Relation
-
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
-
attribute.in(value.arel.ast)
-
when Range
-
attribute.in(value)
-
when ActiveRecord::Base
-
attribute.eq(value.id)
-
when Class
-
# FIXME: I think we need to deprecate this behavior
-
attribute.eq(value.name)
-
else
-
445
attribute.eq(value)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
###
-
# This class encapsulates a Result returned from calling +exec_query+ on any
-
# database connection adapter. For example:
-
#
-
# x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
-
# x # => #<ActiveRecord::Result:0xdeadbeef>
-
1
class Result
-
1
include Enumerable
-
-
1
attr_reader :columns, :rows, :column_types
-
-
1
def initialize(columns, rows, column_types = {})
-
1956
@columns = columns
-
1956
@rows = rows
-
1956
@hash_rows = nil
-
1956
@column_types = column_types
-
end
-
-
1
def each
-
2723
hash_rows.each { |row| yield row }
-
end
-
-
1
def to_hash
-
10
hash_rows
-
end
-
-
1
alias :map! :map
-
1
alias :collect! :map
-
-
# Returns true if there are no records.
-
1
def empty?
-
rows.empty?
-
end
-
-
1
def to_ary
-
hash_rows
-
end
-
-
1
def [](idx)
-
hash_rows[idx]
-
end
-
-
1
def last
-
hash_rows.last
-
end
-
-
1
def initialize_copy(other)
-
1090
@columns = columns.dup
-
1090
@rows = rows.dup
-
1090
@hash_rows = nil
-
end
-
-
1
private
-
1
def hash_rows
-
@hash_rows ||=
-
begin
-
# We freeze the strings to prevent them getting duped when
-
# used as keys in ActiveRecord::Base's @attributes hash
-
8420
columns = @columns.map { |c| c.dup.freeze }
-
1671
@rows.map { |row|
-
# In the past we used Hash[columns.zip(row)]
-
# though elegant, the verbose way is much more efficient
-
# both time and memory wise cause it avoids a big array allocation
-
# this method is called a lot and needs to be micro optimised
-
1116
hash = {}
-
-
1116
index = 0
-
1116
length = columns.length
-
-
1116
while index < length
-
5778
hash[columns[index]] = row[index]
-
5778
index += 1
-
end
-
-
1116
hash
-
}
-
1671
end
-
end
-
end
-
end
-
1
require 'active_support/per_thread_registry'
-
-
1
module ActiveRecord
-
# This is a thread locals registry for Active Record. For example:
-
#
-
# ActiveRecord::RuntimeRegistry.connection_handler
-
#
-
# returns the connection handler local to the current thread.
-
#
-
# See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
-
# for further details.
-
1
class RuntimeRegistry # :nodoc:
-
1
extend ActiveSupport::PerThreadRegistry
-
-
1
attr_accessor :connection_handler, :sql_runtime, :connection_id
-
end
-
end
-
1
module ActiveRecord
-
1
module Sanitization
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def quote_value(value, column = nil) #:nodoc:
-
connection.quote(value,column)
-
end
-
-
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
-
1
def sanitize(object) #:nodoc:
-
connection.quote(object)
-
end
-
-
1
protected
-
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a WHERE clause.
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
-
# { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
-
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
-
1
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
-
750
return nil if condition.blank?
-
-
750
case condition
-
82
when Array; sanitize_sql_array(condition)
-
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
-
668
else condition
-
end
-
end
-
1
alias_method :sanitize_sql, :sanitize_sql_for_conditions
-
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a SET clause.
-
# { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
-
1
def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
-
case assignments
-
when Array; sanitize_sql_array(assignments)
-
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
-
else assignments
-
end
-
end
-
-
# Accepts a hash of SQL conditions and replaces those attributes
-
# that correspond to a +composed_of+ relationship with their expanded
-
# aggregate attribute values.
-
# Given:
-
# class Person < ActiveRecord::Base
-
# composed_of :address, class_name: "Address",
-
# mapping: [%w(address_street street), %w(address_city city)]
-
# end
-
# Then:
-
# { address: Address.new("813 abc st.", "chicago") }
-
# # => { address_street: "813 abc st.", address_city: "chicago" }
-
1
def expand_hash_conditions_for_aggregates(attrs)
-
445
expanded_attrs = {}
-
445
attrs.each do |attr, value|
-
445
if aggregation = reflect_on_aggregation(attr.to_sym)
-
mapping = aggregation.mapping
-
mapping.each do |field_attr, aggregate_attr|
-
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
-
expanded_attrs[field_attr] = value
-
else
-
expanded_attrs[field_attr] = value.send(aggregate_attr)
-
end
-
end
-
else
-
445
expanded_attrs[attr] = value
-
end
-
end
-
445
expanded_attrs
-
end
-
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
-
# { name: "foo'bar", group_id: 4 }
-
# # => "name='foo''bar' and group_id= 4"
-
# { status: nil, group_id: [1,2,3] }
-
# # => "status IS NULL and group_id IN (1,2,3)"
-
# { age: 13..18 }
-
# # => "age BETWEEN 13 AND 18"
-
# { 'other_records.id' => 7 }
-
# # => "`other_records`.`id` = 7"
-
# { other_records: { id: 7 } }
-
# # => "`other_records`.`id` = 7"
-
# And for value objects on a composed_of relationship:
-
# { address: Address.new("123 abc st.", "chicago") }
-
# # => "address_street='123 abc st.' and address_city='chicago'"
-
1
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
-
attrs = expand_hash_conditions_for_aggregates(attrs)
-
-
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
-
PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
-
connection.visitor.accept b
-
}.join(' AND ')
-
end
-
1
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
-
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
-
# { status: nil, group_id: 1 }
-
# # => "status = NULL , group_id = 1"
-
1
def sanitize_sql_hash_for_assignment(attrs, table)
-
c = connection
-
attrs.map do |attr, value|
-
"#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
-
end.join(', ')
-
end
-
-
# Accepts an array of conditions. The array has each value
-
# sanitized and interpolated into the SQL statement.
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
-
1
def sanitize_sql_array(ary)
-
82
statement, *values = ary
-
82
if values.first.is_a?(Hash) && statement =~ /:\w+/
-
82
replace_named_bind_variables(statement, values.first)
-
elsif statement.include?('?')
-
replace_bind_variables(statement, values)
-
elsif statement.blank?
-
statement
-
else
-
statement % values.collect { |value| connection.quote_string(value.to_s) }
-
end
-
end
-
-
1
alias_method :sanitize_conditions, :sanitize_sql
-
-
1
def replace_bind_variables(statement, values) #:nodoc:
-
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
-
bound = values.dup
-
c = connection
-
statement.gsub('?') do
-
replace_bind_variable(bound.shift, c)
-
end
-
end
-
-
1
def replace_bind_variable(value, c = connection) #:nodoc:
-
253
if ActiveRecord::Relation === value
-
value.to_sql
-
else
-
253
quote_bound_value(value, c)
-
end
-
end
-
-
1
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
-
82
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
-
253
if $1 == ':' # skip postgresql casts
-
$& # return the whole match
-
elsif bind_vars.include?(match = $2.to_sym)
-
253
replace_bind_variable(bind_vars[match])
-
else
-
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
-
end
-
end
-
end
-
-
1
def quote_bound_value(value, c = connection, column = nil) #:nodoc:
-
253
if column
-
c.quote(value, column)
-
253
elsif value.respond_to?(:map) && !value.acts_like?(:string)
-
if value.respond_to?(:empty?) && value.empty?
-
c.quote(nil)
-
else
-
value.map { |v| c.quote(v) }.join(',')
-
end
-
else
-
253
c.quote(value)
-
end
-
end
-
-
1
def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
-
unless expected == provided
-
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
-
end
-
end
-
end
-
-
# TODO: Deprecate this
-
1
def quoted_id
-
self.class.quote_value(id, column_for_attribute(self.class.primary_key))
-
end
-
end
-
end
-
1
require 'active_record/scoping/default'
-
1
require 'active_record/scoping/named'
-
1
require 'active_record/base'
-
-
1
module ActiveRecord
-
1
class SchemaMigration < ActiveRecord::Base
-
-
1
def self.table_name
-
6
"#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}"
-
end
-
-
1
def self.index_name
-
"#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
-
end
-
-
1
def self.create_table(limit=nil)
-
unless connection.table_exists?(table_name)
-
version_options = {null: false}
-
version_options[:limit] = limit if limit
-
-
connection.create_table(table_name, id: false) do |t|
-
t.column :version, :string, version_options
-
end
-
connection.add_index table_name, :version, unique: true, name: index_name
-
end
-
end
-
-
1
def self.drop_table
-
if connection.table_exists?(table_name)
-
connection.remove_index table_name, name: index_name
-
connection.drop_table(table_name)
-
end
-
end
-
-
1
def version
-
11
super.to_i
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Scoping
-
1
module Default
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Stores the default scope for the class.
-
1
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
-
-
1
self.default_scopes = []
-
-
1
def self.default_scopes?
-
ActiveSupport::Deprecation.warn(
-
"#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
-
)
-
-
!!self.default_scopes
-
end
-
end
-
-
1
module ClassMethods
-
# Returns a scope for the model without the +default_scope+.
-
#
-
# class Post < ActiveRecord::Base
-
# def self.default_scope
-
# where published: true
-
# end
-
# end
-
#
-
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
-
# Post.unscoped.all # Fires "SELECT * FROM posts"
-
#
-
# This method also accepts a block. All queries inside the block will
-
# not use the +default_scope+:
-
#
-
# Post.unscoped {
-
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
-
# }
-
1
def unscoped
-
2445
block_given? ? relation.scoping { yield } : relation
-
end
-
-
1
def before_remove_const #:nodoc:
-
self.current_scope = nil
-
end
-
-
1
protected
-
-
# Use this macro in your model to set a default scope for all operations on
-
# the model.
-
#
-
# class Article < ActiveRecord::Base
-
# default_scope { where(published: true) }
-
# end
-
#
-
# Article.all # => SELECT * FROM articles WHERE published = true
-
#
-
# The +default_scope+ is also applied while creating/building a record.
-
# It is not applied while updating a record.
-
#
-
# Article.new.published # => true
-
# Article.create.published # => true
-
#
-
# (You can also pass any object which responds to +call+ to the
-
# +default_scope+ macro, and it will be called when building the
-
# default scope.)
-
#
-
# If you use multiple +default_scope+ declarations in your model then
-
# they will be merged together:
-
#
-
# class Article < ActiveRecord::Base
-
# default_scope { where(published: true) }
-
# default_scope { where(rating: 'G') }
-
# end
-
#
-
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
-
#
-
# This is also the case with inheritance and module includes where the
-
# parent or module defines a +default_scope+ and the child or including
-
# class defines a second one.
-
#
-
# If you need to do more complex things with a default scope, you can
-
# alternatively define it as a class method:
-
#
-
# class Article < ActiveRecord::Base
-
# def self.default_scope
-
# # Should return a scope, you can call 'super' here etc.
-
# end
-
# end
-
1
def default_scope(scope = nil)
-
2
scope = Proc.new if block_given?
-
-
2
if scope.is_a?(Relation) || !scope.respond_to?(:call)
-
ActiveSupport::Deprecation.warn(
-
"Calling #default_scope without a block is deprecated. For example instead " \
-
"of `default_scope where(color: 'red')`, please use " \
-
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
-
"self.default_scope.)"
-
)
-
end
-
-
2
self.default_scopes += [scope]
-
end
-
-
1
def build_default_scope # :nodoc:
-
3495
if !Base.is_a?(method(:default_scope).owner)
-
# The user has defined their own default scope method, so call that
-
evaluate_default_scope { default_scope }
-
3495
elsif default_scopes.any?
-
523
evaluate_default_scope do
-
523
default_scopes.inject(relation) do |default_scope, scope|
-
523
if !scope.is_a?(Relation) && scope.respond_to?(:call)
-
1046
default_scope.merge(unscoped { scope.call })
-
else
-
default_scope.merge(scope)
-
end
-
end
-
end
-
end
-
end
-
-
1
def ignore_default_scope? # :nodoc:
-
523
ScopeRegistry.value_for(:ignore_default_scope, self)
-
end
-
-
1
def ignore_default_scope=(ignore) # :nodoc:
-
1046
ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
-
end
-
-
# The ignore_default_scope flag is used to prevent an infinite recursion
-
# situation where a default scope references a scope which has a default
-
# scope which references a scope...
-
1
def evaluate_default_scope # :nodoc:
-
523
return if ignore_default_scope?
-
-
523
begin
-
523
self.ignore_default_scope = true
-
523
yield
-
ensure
-
523
self.ignore_default_scope = false
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
-
1
module ActiveRecord
-
# = Active Record \Named \Scopes
-
1
module Scoping
-
1
module Named
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Returns an <tt>ActiveRecord::Relation</tt> scope object.
-
#
-
# posts = Post.all
-
# posts.size # Fires "select count(*) from posts" and returns the count
-
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
-
#
-
# fruits = Fruit.all
-
# fruits = fruits.where(color: 'red') if options[:red_only]
-
# fruits = fruits.limit(10) if limited?
-
#
-
# You can define a scope that applies to all finders using
-
# <tt>ActiveRecord::Base.default_scope</tt>.
-
1
def all
-
2772
if current_scope
-
602
current_scope.clone
-
else
-
2170
scope = relation
-
2170
scope.default_scoped = true
-
2170
scope
-
end
-
end
-
-
# Collects attributes from scopes that should be applied when creating
-
# an AR instance for the particular class this is called on.
-
1
def scope_attributes # :nodoc:
-
160
if current_scope
-
current_scope.scope_for_create
-
else
-
160
scope = relation
-
160
scope.default_scoped = true
-
160
scope.scope_for_create
-
end
-
end
-
-
# Are there default attributes associated with this scope?
-
1
def scope_attributes? # :nodoc:
-
454
current_scope || default_scopes.any?
-
end
-
-
# Adds a class method for retrieving and querying objects. A \scope
-
# represents a narrowing of a database query, such as
-
# <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') }
-
# scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
-
# end
-
#
-
# The above calls to +scope+ define class methods <tt>Shirt.red</tt> and
-
# <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
-
# represents the query <tt>Shirt.where(color: 'red')</tt>.
-
#
-
# You should always pass a callable object to the scopes defined
-
# with +scope+. This ensures that the scope is re-evaluated each
-
# time it is called.
-
#
-
# Note that this is simply 'syntactic sugar' for defining an actual
-
# class method:
-
#
-
# class Shirt < ActiveRecord::Base
-
# def self.red
-
# where(color: 'red')
-
# end
-
# end
-
#
-
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
-
# <tt>Shirt.red</tt> is not an Array; it resembles the association object
-
# constructed by a +has_many+ declaration. For instance, you can invoke
-
# <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
-
# <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
-
# association objects, named \scopes act like an Array, implementing
-
# Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
-
# and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
-
# <tt>Shirt.red</tt> really was an Array.
-
#
-
# These named \scopes are composable. For instance,
-
# <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
-
# both red and dry clean only. Nested finds and calculations also work
-
# with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
-
# returns the number of garments for which these criteria obtain.
-
# Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
-
#
-
# All scopes are available as class methods on the ActiveRecord::Base
-
# descendant upon which the \scopes were defined. But they are also
-
# available to +has_many+ associations. If,
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :shirts
-
# end
-
#
-
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
-
# Elton's red, dry clean only shirts.
-
#
-
# \Named scopes can also have extensions, just as with +has_many+
-
# declarations:
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') } do
-
# def dom_id
-
# 'red_shirts'
-
# end
-
# end
-
# end
-
#
-
# Scopes can also be used while creating/building a record.
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# end
-
#
-
# Article.published.new.published # => true
-
# Article.published.create.published # => true
-
#
-
# \Class methods on your model are automatically available
-
# on scopes. Assuming the following setup:
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# scope :featured, -> { where(featured: true) }
-
#
-
# def self.latest_article
-
# order('published_at desc').first
-
# end
-
#
-
# def self.titles
-
# pluck(:title)
-
# end
-
# end
-
#
-
# We are able to call the methods like this:
-
#
-
# Article.published.featured.latest_article
-
# Article.featured.titles
-
1
def scope(name, body, &block)
-
1
extension = Module.new(&block) if block
-
-
# Check body.is_a?(Relation) to prevent the relation actually being
-
# loaded by respond_to?
-
1
if body.is_a?(Relation) || !body.respond_to?(:call)
-
ActiveSupport::Deprecation.warn(
-
"Using #scope without passing a callable object is deprecated. For " \
-
"example `scope :red, where(color: 'red')` should be changed to " \
-
"`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \
-
"in the former usage and it makes the implementation more complicated " \
-
"and buggy. (If you prefer, you can just define a class method named " \
-
"`self.red`.)"
-
)
-
end
-
-
1
singleton_class.send(:define_method, name) do |*args|
-
75
if body.respond_to?(:call)
-
150
scope = all.scoping { body.call(*args) }
-
75
scope = scope.extending(extension) if extension
-
else
-
scope = body
-
end
-
-
75
scope || all
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord #:nodoc:
-
# = Active Record Serialization
-
1
module Serialization
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serializers::JSON
-
-
1
included do
-
1
self.include_root_in_json = false
-
end
-
-
1
def serializable_hash(options = nil)
-
options = options.try(:clone) || {}
-
-
options[:except] = Array(options[:except]).map { |n| n.to_s }
-
options[:except] |= Array(self.class.inheritance_column)
-
-
super(options)
-
end
-
end
-
end
-
-
1
require 'active_record/serializers/xml_serializer'
-
1
require 'active_support/core_ext/hash/conversions'
-
-
1
module ActiveRecord #:nodoc:
-
1
module Serialization
-
1
include ActiveModel::Serializers::Xml
-
-
# Builds an XML document to represent the model. Some configuration is
-
# available through +options+. However more complicated cases should
-
# override ActiveRecord::Base#to_xml.
-
#
-
# By default the generated XML document will include the processing
-
# instruction and all the object's attributes. For example:
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <topic>
-
# <title>The First Topic</title>
-
# <author-name>David</author-name>
-
# <id type="integer">1</id>
-
# <approved type="boolean">false</approved>
-
# <replies-count type="integer">0</replies-count>
-
# <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
-
# <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
-
# <content>Have a nice day</content>
-
# <author-email-address>david@loudthinking.com</author-email-address>
-
# <parent-id></parent-id>
-
# <last-read type="date">2004-04-15</last-read>
-
# </topic>
-
#
-
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
-
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
-
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
-
# +attributes+ method. The default is to dasherize all column names, but you
-
# can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
-
# to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
-
# To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
-
#
-
# For instance:
-
#
-
# topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ])
-
#
-
# <topic>
-
# <title>The First Topic</title>
-
# <author-name>David</author-name>
-
# <approved type="boolean">false</approved>
-
# <content>Have a nice day</content>
-
# <author-email-address>david@loudthinking.com</author-email-address>
-
# <parent-id></parent-id>
-
# <last-read type="date">2004-04-15</last-read>
-
# </topic>
-
#
-
# To include first level associations use <tt>:include</tt>:
-
#
-
# firm.to_xml include: [ :account, :clients ]
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <firm>
-
# <id type="integer">1</id>
-
# <rating type="integer">1</rating>
-
# <name>37signals</name>
-
# <clients type="array">
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Summit</name>
-
# </client>
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Microsoft</name>
-
# </client>
-
# </clients>
-
# <account>
-
# <id type="integer">1</id>
-
# <credit-limit type="integer">50</credit-limit>
-
# </account>
-
# </firm>
-
#
-
# Additionally, the record being serialized will be passed to a Proc's second
-
# parameter. This allows for ad hoc additions to the resultant document that
-
# incorporate the context of the record being serialized. And by leveraging the
-
# closure created by a Proc, to_xml can be used to add elements that normally fall
-
# outside of the scope of the model -- for example, generating and appending URLs
-
# associated with models.
-
#
-
# proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
-
# firm.to_xml procs: [ proc ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <name-reverse>slangis73</name-reverse>
-
# </firm>
-
#
-
# To include deeper levels of associations pass a hash like this:
-
#
-
# firm.to_xml include: {account: {}, clients: {include: :address}}
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <firm>
-
# <id type="integer">1</id>
-
# <rating type="integer">1</rating>
-
# <name>37signals</name>
-
# <clients type="array">
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Summit</name>
-
# <address>
-
# ...
-
# </address>
-
# </client>
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Microsoft</name>
-
# <address>
-
# ...
-
# </address>
-
# </client>
-
# </clients>
-
# <account>
-
# <id type="integer">1</id>
-
# <credit-limit type="integer">50</credit-limit>
-
# </account>
-
# </firm>
-
#
-
# To include any methods on the model being called use <tt>:methods</tt>:
-
#
-
# firm.to_xml methods: [ :calculated_earnings, :real_earnings ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <calculated-earnings>100000000000000000</calculated-earnings>
-
# <real-earnings>5</real-earnings>
-
# </firm>
-
#
-
# To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
-
# modified version of the options hash that was given to +to_xml+:
-
#
-
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
-
# firm.to_xml procs: [ proc ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <abc>def</abc>
-
# </firm>
-
#
-
# Alternatively, you can yield the builder object as part of the +to_xml+ call:
-
#
-
# firm.to_xml do |xml|
-
# xml.creator do
-
# xml.first_name "David"
-
# xml.last_name "Heinemeier Hansson"
-
# end
-
# end
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <creator>
-
# <first_name>David</first_name>
-
# <last_name>Heinemeier Hansson</last_name>
-
# </creator>
-
# </firm>
-
#
-
# As noted above, you may override +to_xml+ in your ActiveRecord::Base
-
# subclasses to have complete control about what's generated. The general
-
# form of doing this is:
-
#
-
# class IHaveMyOwnXML < ActiveRecord::Base
-
# def to_xml(options = {})
-
# require 'builder'
-
# options[:indent] ||= 2
-
# xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
-
# xml.instruct! unless options[:skip_instruct]
-
# xml.level_one do
-
# xml.tag!(:second_level, 'content')
-
# end
-
# end
-
# end
-
1
def to_xml(options = {}, &block)
-
XmlSerializer.new(self, options).serialize(&block)
-
end
-
end
-
-
1
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
-
1
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
-
1
def compute_type
-
klass = @serializable.class
-
type = if klass.serialized_attributes.key?(name)
-
super
-
elsif klass.columns_hash.key?(name)
-
klass.columns_hash[name].type
-
else
-
NilClass
-
end
-
-
{ :text => :string,
-
:time => :datetime }[type] || type
-
end
-
1
protected :compute_type
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
-
# It's like a simple key/value store baked into your record when you don't care about being able to
-
# query that store outside the context of a single record.
-
#
-
# You can then declare accessors to this store that are then accessible just like any other attribute
-
# of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
-
# already built around just accessing attributes on the model.
-
#
-
# Make sure that you declare the database column used for the serialized store as a text, so there's
-
# plenty of room.
-
#
-
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
-
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
-
#
-
# Examples:
-
#
-
# class User < ActiveRecord::Base
-
# store :settings, accessors: [ :color, :homepage ], coder: JSON
-
# end
-
#
-
# u = User.new(color: 'black', homepage: '37signals.com')
-
# u.color # Accessor stored attribute
-
# u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
-
#
-
# # There is no difference between strings and symbols for accessing custom attributes
-
# u.settings[:country] # => 'Denmark'
-
# u.settings['country'] # => 'Denmark'
-
#
-
# # Add additional accessors to an existing store through store_accessor
-
# class SuperUser < User
-
# store_accessor :settings, :privileges, :servants
-
# end
-
#
-
# The stored attribute names can be retrieved using +stored_attributes+.
-
#
-
# User.stored_attributes[:settings] # [:color, :homepage]
-
#
-
# == Overwriting default accessors
-
#
-
# All stored values are automatically available through accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling <tt>super</tt>
-
# to actually change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses a stored integer to hold the volume adjustment of the song
-
# store :settings, accessors: [:volume_adjustment]
-
#
-
# def volume_adjustment=(decibels)
-
# super(decibels.to_i)
-
# end
-
#
-
# def volume_adjustment
-
# super.to_i
-
# end
-
# end
-
1
module Store
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :stored_attributes, instance_accessor: false
-
1
self.stored_attributes = {}
-
end
-
-
1
module ClassMethods
-
1
def store(store_attribute, options = {})
-
serialize store_attribute, IndifferentCoder.new(options[:coder])
-
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
-
end
-
-
1
def store_accessor(store_attribute, *keys)
-
keys = keys.flatten
-
-
_store_accessors_module.module_eval do
-
keys.each do |key|
-
define_method("#{key}=") do |value|
-
write_store_attribute(store_attribute, key, value)
-
end
-
-
define_method(key) do
-
read_store_attribute(store_attribute, key)
-
end
-
end
-
end
-
-
self.stored_attributes[store_attribute] ||= []
-
self.stored_attributes[store_attribute] |= keys
-
end
-
-
1
def _store_accessors_module
-
@_store_accessors_module ||= begin
-
mod = Module.new
-
include mod
-
mod
-
end
-
end
-
end
-
-
1
protected
-
1
def read_store_attribute(store_attribute, key)
-
attribute = initialize_store_attribute(store_attribute)
-
attribute[key]
-
end
-
-
1
def write_store_attribute(store_attribute, key, value)
-
attribute = initialize_store_attribute(store_attribute)
-
if value != attribute[key]
-
send :"#{store_attribute}_will_change!"
-
attribute[key] = value
-
end
-
end
-
-
1
private
-
1
def initialize_store_attribute(store_attribute)
-
attribute = send(store_attribute)
-
unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
-
attribute = IndifferentCoder.as_indifferent_hash(attribute)
-
send :"#{store_attribute}=", attribute
-
end
-
attribute
-
end
-
-
1
class IndifferentCoder # :nodoc:
-
1
def initialize(coder_or_class_name)
-
@coder =
-
if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
-
coder_or_class_name
-
else
-
ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
-
end
-
end
-
-
1
def dump(obj)
-
@coder.dump self.class.as_indifferent_hash(obj)
-
end
-
-
1
def load(yaml)
-
self.class.as_indifferent_hash @coder.load(yaml || '')
-
end
-
-
1
def self.as_indifferent_hash(obj)
-
case obj
-
when ActiveSupport::HashWithIndifferentAccess
-
obj
-
when Hash
-
obj.with_indifferent_access
-
else
-
ActiveSupport::HashWithIndifferentAccess.new
-
end
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
# = Active Record Timestamp
-
#
-
# Active Record automatically timestamps create and update operations if the
-
# table has fields named <tt>created_at/created_on</tt> or
-
# <tt>updated_at/updated_on</tt>.
-
#
-
# Timestamping can be turned off by setting:
-
#
-
# config.active_record.record_timestamps = false
-
#
-
# Timestamps are in UTC by default but you can use the local timezone by setting:
-
#
-
# config.active_record.default_timezone = :local
-
#
-
# == Time Zone aware attributes
-
#
-
# By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
-
#
-
# config.active_record.time_zone_aware_attributes = true
-
#
-
# This feature can easily be turned off by assigning value <tt>false</tt> .
-
#
-
# If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone
-
# when reading certain attributes then you can do following:
-
#
-
# class Topic < ActiveRecord::Base
-
# self.skip_time_zone_conversion_for_attributes = [:written_on]
-
# end
-
1
module Timestamp
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :record_timestamps
-
1
self.record_timestamps = true
-
end
-
-
1
def initialize_dup(other) # :nodoc:
-
1
clear_timestamp_attributes
-
1
super
-
end
-
-
1
private
-
-
1
def create_record
-
291
if self.record_timestamps
-
291
current_time = current_time_from_proper_timezone
-
-
291
all_timestamp_attributes.each do |column|
-
1164
if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil?
-
570
write_attribute(column.to_s, current_time)
-
end
-
end
-
end
-
-
291
super
-
end
-
-
1
def update_record(*args)
-
96
if should_record_timestamps?
-
74
current_time = current_time_from_proper_timezone
-
-
74
timestamp_attributes_for_update_in_model.each do |column|
-
74
column = column.to_s
-
74
next if attribute_changed?(column)
-
74
write_attribute(column, current_time)
-
end
-
end
-
96
super
-
end
-
-
1
def should_record_timestamps?
-
96
self.record_timestamps && (!partial_writes? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
-
end
-
-
1
def timestamp_attributes_for_create_in_model
-
3
timestamp_attributes_for_create.select { |c| self.class.column_names.include?(c.to_s) }
-
end
-
-
1
def timestamp_attributes_for_update_in_model
-
225
timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) }
-
end
-
-
1
def all_timestamp_attributes_in_model
-
1
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
-
end
-
-
1
def timestamp_attributes_for_update
-
366
[:updated_at, :updated_on]
-
end
-
-
1
def timestamp_attributes_for_create
-
292
[:created_at, :created_on]
-
end
-
-
1
def all_timestamp_attributes
-
291
timestamp_attributes_for_create + timestamp_attributes_for_update
-
end
-
-
1
def max_updated_column_timestamp
-
if (timestamps = timestamp_attributes_for_update.map { |attr| self[attr] }.compact).present?
-
timestamps.map { |ts| ts.to_time }.max
-
end
-
end
-
-
1
def current_time_from_proper_timezone
-
365
self.class.default_timezone == :utc ? Time.now.utc : Time.now
-
end
-
-
# Clear attributes and changed_attributes
-
1
def clear_timestamp_attributes
-
1
all_timestamp_attributes_in_model.each do |attribute_name|
-
2
self[attribute_name] = nil
-
2
changed_attributes.delete(attribute_name)
-
end
-
end
-
end
-
end
-
1
require 'thread'
-
-
1
module ActiveRecord
-
# See ActiveRecord::Transactions::ClassMethods for documentation.
-
1
module Transactions
-
1
extend ActiveSupport::Concern
-
1
ACTIONS = [:create, :destroy, :update]
-
-
1
class TransactionError < ActiveRecordError # :nodoc:
-
end
-
-
1
included do
-
1
define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
-
end
-
-
# = Active Record Transactions
-
#
-
# Transactions are protective blocks where SQL statements are only permanent
-
# if they can all succeed as one atomic action. The classic example is a
-
# transfer between two accounts where you can only have a deposit if the
-
# withdrawal succeeded and vice versa. Transactions enforce the integrity of
-
# the database and guard the data against program errors or database
-
# break-downs. So basically you should use transaction blocks whenever you
-
# have a number of statements that must be executed together or not at all.
-
#
-
# For example:
-
#
-
# ActiveRecord::Base.transaction do
-
# david.withdrawal(100)
-
# mary.deposit(100)
-
# end
-
#
-
# This example will only take money from David and give it to Mary if neither
-
# +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
-
# ROLLBACK that returns the database to the state before the transaction
-
# began. Be aware, though, that the objects will _not_ have their instance
-
# data returned to their pre-transactional state.
-
#
-
# == Different Active Record classes in a single transaction
-
#
-
# Though the transaction class method is called on some Active Record class,
-
# the objects within the transaction block need not all be instances of
-
# that class. This is because transactions are per-database connection, not
-
# per-model.
-
#
-
# In this example a +balance+ record is transactionally saved even
-
# though +transaction+ is called on the +Account+ class:
-
#
-
# Account.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# The +transaction+ method is also available as a model instance method.
-
# For example, you can also do this:
-
#
-
# balance.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# == Transactions are not distributed across database connections
-
#
-
# A transaction acts on a single database connection. If you have
-
# multiple class-specific databases, the transaction will not protect
-
# interaction among them. One workaround is to begin a transaction
-
# on each class whose models you alter:
-
#
-
# Student.transaction do
-
# Course.transaction do
-
# course.enroll(student)
-
# student.units += course.units
-
# end
-
# end
-
#
-
# This is a poor solution, but fully distributed transactions are beyond
-
# the scope of Active Record.
-
#
-
# == +save+ and +destroy+ are automatically wrapped in a transaction
-
#
-
# Both +save+ and +destroy+ come wrapped in a transaction that ensures
-
# that whatever you do in validations or callbacks will happen under its
-
# protected cover. So you can use validations to check for values that
-
# the transaction depends on or you can raise exceptions in the callbacks
-
# to rollback, including <tt>after_*</tt> callbacks.
-
#
-
# As a consequence changes to the database are not seen outside your connection
-
# until the operation is complete. For example, if you try to update the index
-
# of a search engine in +after_save+ the indexer won't see the updated record.
-
# The +after_commit+ callback is the only one that is triggered once the update
-
# is committed. See below.
-
#
-
# == Exception handling and rolling back
-
#
-
# Also have in mind that exceptions thrown within a transaction block will
-
# be propagated (after triggering the ROLLBACK), so you should be ready to
-
# catch those in your application code.
-
#
-
# One exception is the <tt>ActiveRecord::Rollback</tt> exception, which will trigger
-
# a ROLLBACK when raised, but not be re-raised by the transaction block.
-
#
-
# *Warning*: one should not catch <tt>ActiveRecord::StatementInvalid</tt> exceptions
-
# inside a transaction block. <tt>ActiveRecord::StatementInvalid</tt> exceptions indicate that an
-
# error occurred at the database level, for example when a unique constraint
-
# is violated. On some database systems, such as PostgreSQL, database errors
-
# inside a transaction cause the entire transaction to become unusable
-
# until it's restarted from the beginning. Here is an example which
-
# demonstrates the problem:
-
#
-
# # Suppose that we have a Number model with a unique column called 'i'.
-
# Number.transaction do
-
# Number.create(i: 0)
-
# begin
-
# # This will raise a unique constraint error...
-
# Number.create(i: 0)
-
# rescue ActiveRecord::StatementInvalid
-
# # ...which we ignore.
-
# end
-
#
-
# # On PostgreSQL, the transaction is now unusable. The following
-
# # statement will cause a PostgreSQL error, even though the unique
-
# # constraint is no longer violated:
-
# Number.create(i: 1)
-
# # => "PGError: ERROR: current transaction is aborted, commands
-
# # ignored until end of transaction block"
-
# end
-
#
-
# One should restart the entire transaction if an
-
# <tt>ActiveRecord::StatementInvalid</tt> occurred.
-
#
-
# == Nested transactions
-
#
-
# +transaction+ calls can be nested. By default, this makes all database
-
# statements in the nested transaction block become part of the parent
-
# transaction. For example, the following behavior may be surprising:
-
#
-
# User.transaction do
-
# User.create(username: 'Kotori')
-
# User.transaction do
-
# User.create(username: 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# creates both "Kotori" and "Nemu". Reason is the <tt>ActiveRecord::Rollback</tt>
-
# exception in the nested block does not issue a ROLLBACK. Since these exceptions
-
# are captured in transaction blocks, the parent block does not see it and the
-
# real transaction is committed.
-
#
-
# In order to get a ROLLBACK for the nested transaction you may ask for a real
-
# sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
-
# the database rolls back to the beginning of the sub-transaction without rolling
-
# back the parent transaction. If we add it to the previous example:
-
#
-
# User.transaction do
-
# User.create(username: 'Kotori')
-
# User.transaction(requires_new: true) do
-
# User.create(username: 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
-
#
-
# Most databases don't support true nested transactions. At the time of
-
# writing, the only database that we're aware of that supports true nested
-
# transactions, is MS-SQL. Because of this, Active Record emulates nested
-
# transactions by using savepoints on MySQL and PostgreSQL. See
-
# http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
-
# for more information about savepoints.
-
#
-
# === Callbacks
-
#
-
# There are two types of callbacks associated with committing and rolling back transactions:
-
# +after_commit+ and +after_rollback+.
-
#
-
# +after_commit+ callbacks are called on every record saved or destroyed within a
-
# transaction immediately after the transaction is committed. +after_rollback+ callbacks
-
# are called on every record saved or destroyed within a transaction immediately after the
-
# transaction or savepoint is rolled back.
-
#
-
# These callbacks are useful for interacting with other systems since you will be guaranteed
-
# that the callback is only executed when the database is in a permanent state. For example,
-
# +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
-
# within a transaction could trigger the cache to be regenerated before the database is updated.
-
#
-
# === Caveats
-
#
-
# If you're on MySQL, then do not use DDL operations in nested transactions
-
# blocks that are emulated with savepoints. That is, do not execute statements
-
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
-
# releases all savepoints upon executing a DDL operation. When +transaction+
-
# is finished and tries to release the savepoint it created earlier, a
-
# database error will occur because the savepoint has already been
-
# automatically released. The following example demonstrates the problem:
-
#
-
# Model.connection.transaction do # BEGIN
-
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
-
# Model.connection.create_table(...) # active_record_1 now automatically released
-
# end # RELEASE savepoint active_record_1
-
# # ^^^^ BOOM! database error!
-
# end
-
#
-
# Note that "TRUNCATE" is also a MySQL DDL statement!
-
1
module ClassMethods
-
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
-
1
def transaction(options = {}, &block)
-
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
-
457
connection.transaction(options, &block)
-
end
-
-
# This callback is called after a record has been created, updated, or destroyed.
-
#
-
# You can specify that the callback should only be fired by a certain action with
-
# the +:on+ option:
-
#
-
# after_commit :do_foo, on: :create
-
# after_commit :do_bar, on: :update
-
# after_commit :do_baz, on: :destroy
-
#
-
# after_commit :do_foo_bar, on: [:create, :update]
-
# after_commit :do_bar_baz, on: [:update, :destroy]
-
#
-
# Note that transactional fixtures do not play well with this feature. Please
-
# use the +test_after_commit+ gem to have these hooks fired in tests.
-
1
def after_commit(*args, &block)
-
set_options_for_callbacks!(args)
-
set_callback(:commit, :after, *args, &block)
-
end
-
-
# This callback is called after a create, update, or destroy are rolled back.
-
#
-
# Please check the documentation of +after_commit+ for options.
-
1
def after_rollback(*args, &block)
-
set_options_for_callbacks!(args)
-
set_callback(:rollback, :after, *args, &block)
-
end
-
-
1
private
-
-
1
def set_options_for_callbacks!(args)
-
options = args.last
-
if options.is_a?(Hash) && options[:on]
-
assert_valid_transaction_action(options[:on])
-
options[:if] = Array(options[:if])
-
fire_on = Array(options[:on])
-
options[:if] << "transaction_include_any_action?(#{fire_on})"
-
end
-
end
-
-
1
def assert_valid_transaction_action(actions)
-
actions = Array(actions)
-
if (actions - ACTIONS).any?
-
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
-
end
-
end
-
end
-
-
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
-
1
def transaction(options = {}, &block)
-
self.class.transaction(options, &block)
-
end
-
-
1
def destroy #:nodoc:
-
24
with_transaction_returning_status { super }
-
end
-
-
1
def save(*) #:nodoc:
-
136
rollback_active_record_state! do
-
272
with_transaction_returning_status { super }
-
end
-
end
-
-
1
def save!(*) #:nodoc:
-
526
with_transaction_returning_status { super }
-
end
-
-
1
def touch(*) #:nodoc:
-
with_transaction_returning_status { super }
-
end
-
-
# Reset id and @new_record if the transaction rolls back.
-
1
def rollback_active_record_state!
-
136
remember_transaction_record_state
-
136
yield
-
rescue Exception
-
restore_transaction_record_state
-
raise
-
ensure
-
136
clear_transaction_record_state
-
end
-
-
# Call the +after_commit+ callbacks.
-
#
-
# Ensure that it is not called if the object was never persisted (failed create),
-
# but call it after the commit of a destroyed object.
-
1
def committed! #:nodoc:
-
30
run_callbacks :commit if destroyed? || persisted?
-
ensure
-
30
@_start_transaction_state.clear
-
end
-
-
# Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
-
# state should be rolled back to the beginning or just to the last savepoint.
-
1
def rolledback!(force_restore_state = false) #:nodoc:
-
8
run_callbacks :rollback
-
ensure
-
8
restore_transaction_record_state(force_restore_state)
-
end
-
-
# Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks
-
# can be called.
-
1
def add_to_transaction
-
417
if self.class.connection.add_transaction_record(self)
-
417
remember_transaction_record_state
-
end
-
end
-
-
# Executes +method+ within a transaction and captures its return value as a
-
# status flag. If the status is true the transaction is committed, otherwise
-
# a ROLLBACK is issued. In any case the status flag is returned.
-
#
-
# This method is available within the context of an ActiveRecord::Base
-
# instance.
-
1
def with_transaction_returning_status
-
417
status = nil
-
417
self.class.transaction do
-
417
add_to_transaction
-
417
begin
-
417
status = yield
-
rescue ActiveRecord::Rollback
-
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
-
status = nil
-
end
-
-
417
raise ActiveRecord::Rollback unless status
-
end
-
417
status
-
end
-
-
1
protected
-
-
# Save the new record state and id of a record so it can be restored later if a transaction fails.
-
1
def remember_transaction_record_state #:nodoc:
-
553
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
-
553
unless @_start_transaction_state.include?(:new_record)
-
374
@_start_transaction_state[:new_record] = @new_record
-
end
-
553
unless @_start_transaction_state.include?(:destroyed)
-
374
@_start_transaction_state[:destroyed] = @destroyed
-
end
-
553
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
-
553
@_start_transaction_state[:frozen?] = @attributes.frozen?
-
end
-
-
# Clear the new record state and id of a record.
-
1
def clear_transaction_record_state #:nodoc:
-
136
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
-
136
@_start_transaction_state.clear if @_start_transaction_state[:level] < 1
-
end
-
-
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
-
1
def restore_transaction_record_state(force = false) #:nodoc:
-
8
unless @_start_transaction_state.empty?
-
8
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
-
8
if @_start_transaction_state[:level] < 1 || force
-
restore_state = @_start_transaction_state
-
was_frozen = restore_state[:frozen?]
-
@attributes = @attributes.dup if @attributes.frozen?
-
@new_record = restore_state[:new_record]
-
@destroyed = restore_state[:destroyed]
-
if restore_state.has_key?(:id)
-
self.id = restore_state[:id]
-
else
-
@attributes.delete(self.class.primary_key)
-
@attributes_cache.delete(self.class.primary_key)
-
end
-
@attributes.freeze if was_frozen
-
@_start_transaction_state.clear
-
end
-
end
-
end
-
-
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
-
1
def transaction_record_state(state) #:nodoc:
-
@_start_transaction_state[state]
-
end
-
-
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
-
1
def transaction_include_any_action?(actions) #:nodoc:
-
actions.any? do |action|
-
case action
-
when :create
-
transaction_record_state(:new_record)
-
when :destroy
-
destroyed?
-
when :update
-
!(transaction_record_state(:new_record) || destroyed?)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Translation
-
1
include ActiveModel::Translation
-
-
# Set the lookup ancestors for ActiveModel.
-
1
def lookup_ancestors #:nodoc:
-
347
klass = self
-
347
classes = [klass]
-
347
return classes if klass == ActiveRecord::Base
-
-
347
while klass != klass.base_class
-
classes << klass = klass.superclass
-
end
-
347
classes
-
end
-
-
# Set the i18n scope to overwrite ActiveModel.
-
1
def i18n_scope #:nodoc:
-
487
:activerecord
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record RecordInvalid
-
#
-
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
-
# +record+ method to retrieve the record which did not validate.
-
#
-
# begin
-
# complex_operation_that_calls_save!_internally
-
# rescue ActiveRecord::RecordInvalid => invalid
-
# puts invalid.record.errors
-
# end
-
1
class RecordInvalid < ActiveRecordError
-
1
attr_reader :record # :nodoc:
-
1
def initialize(record) # :nodoc:
-
@record = record
-
errors = @record.errors.full_messages.join(", ")
-
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
-
end
-
end
-
-
# = Active Record Validations
-
#
-
# Active Record includes the majority of its validations from <tt>ActiveModel::Validations</tt>
-
# all of which accept the <tt>:on</tt> argument to define the context where the
-
# validations are active. Active Record will always supply either the context of
-
# <tt>:create</tt> or <tt>:update</tt> dependent on whether the model is a
-
# <tt>new_record?</tt>.
-
1
module Validations
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Validations
-
-
1
module ClassMethods
-
# Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
-
# so an exception is raised if the record is invalid.
-
1
def create!(attributes = nil, &block)
-
if attributes.is_a?(Array)
-
attributes.collect { |attr| create!(attr, &block) }
-
else
-
object = new(attributes)
-
yield(object) if block_given?
-
object.save!
-
object
-
end
-
end
-
end
-
-
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
-
# The regular Base#save method is replaced with this when the validations
-
# module is mixed in, which it is by default.
-
1
def save(options={})
-
136
perform_validations(options) ? super : false
-
end
-
-
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
-
# exception instead of returning +false+ if the record is not valid.
-
1
def save!(options={})
-
263
perform_validations(options) ? super : raise(RecordInvalid.new(self))
-
end
-
-
# Runs all the validations within the specified context. Returns +true+ if
-
# no errors are found, +false+ otherwise.
-
#
-
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
-
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
-
#
-
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
-
# some <tt>:on</tt> option will only run in the specified context.
-
1
def valid?(context = nil)
-
363
context ||= (new_record? ? :create : :update)
-
363
output = super(context)
-
363
errors.empty? && output
-
end
-
-
1
protected
-
-
1
def perform_validations(options={}) # :nodoc:
-
399
options[:validate] == false || valid?(options[:context])
-
end
-
end
-
end
-
-
1
require "active_record/validations/associated"
-
1
require "active_record/validations/uniqueness"
-
1
require "active_record/validations/presence"
-
1
module ActiveRecord
-
1
module Validations
-
1
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
-
1
def validate_each(record, attribute, value)
-
if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
-
record.errors.add(attribute, :invalid, options.merge(:value => value))
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Validates whether the associated object or objects are all valid.
-
# Works with any kind of association.
-
#
-
# class Book < ActiveRecord::Base
-
# has_many :pages
-
# belongs_to :library
-
#
-
# validates_associated :pages, :library
-
# end
-
#
-
# WARNING: This validation must not be used on both ends of an association.
-
# Doing so will lead to a circular dependency and cause infinite recursion.
-
#
-
# NOTE: This validation will not fail if the association hasn't been
-
# assigned. If you want to ensure that the association is both present and
-
# guaranteed to be valid, you also need to use +validates_presence_of+.
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
-
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
-
# validation contexts by default (+nil+), other options are <tt>:create</tt>
-
# and <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validates_associated(*attr_names)
-
validates_with AssociatedValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Validations
-
1
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
-
1
def validate(record)
-
1478
super
-
1478
attributes.each do |attribute|
-
1478
next unless record.class.reflect_on_association(attribute)
-
associated_records = Array.wrap(record.send(attribute))
-
-
# Superclass validates presence. Ensure present records aren't about to be destroyed.
-
if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
-
record.errors.add(attribute, :blank, options)
-
end
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Validates that the specified attributes are not blank (as defined by
-
# Object#blank?), and, if the attribute is an association, that the
-
# associated object is not marked for destruction. Happens by default
-
# on save.
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :face
-
# validates_presence_of :face
-
# end
-
#
-
# The face attribute must be in the object and it cannot be blank or marked
-
# for destruction.
-
#
-
# If you want to validate the presence of a boolean field (where the real values
-
# are true and false), you will want to use
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
-
#
-
# This is due to the way Object#blank? handles boolean values:
-
# <tt>false.blank? # => true</tt>.
-
#
-
# This validator defers to the ActiveModel validation for presence, adding the
-
# check to see that an associated object is not marked for destruction. This
-
# prevents the parent object from validating successfully and saving, which then
-
# deletes the associated object, thus putting the parent object into an invalid
-
# state.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
-
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
-
# validation contexts by default (+nil+), other options are <tt>:create</tt>
-
# and <tt>:update</tt>.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
-
# the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
-
# <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
-
# or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
-
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
-
1
def validates_presence_of(*attr_names)
-
2
validates_with PresenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Validations
-
1
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
-
1
def initialize(options)
-
2
if options[:conditions] && !options[:conditions].respond_to?(:call)
-
raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
-
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
-
end
-
2
super({ case_sensitive: true }.merge!(options))
-
end
-
-
# Unfortunately, we have to tie Uniqueness validators to a class.
-
1
def setup(klass)
-
2
@klass = klass
-
end
-
-
1
def validate_each(record, attribute, value)
-
518
finder_class = find_finder_class_for(record)
-
518
table = finder_class.arel_table
-
518
value = deserialize_attribute(record, attribute, value)
-
-
518
relation = build_relation(finder_class, table, attribute, value)
-
518
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
-
518
relation = scope_relation(record, table, relation)
-
518
relation = finder_class.unscoped.where(relation)
-
518
relation = relation.merge(options[:conditions]) if options[:conditions]
-
-
518
if relation.exists?
-
2
error_options = options.except(:case_sensitive, :scope, :conditions)
-
2
error_options[:value] = value
-
-
2
record.errors.add(attribute, :taken, error_options)
-
end
-
end
-
-
1
protected
-
-
# The check for an existing value should be run from a class that
-
# isn't abstract. This means working down from the current class
-
# (self), to the first non-abstract class. Since classes don't know
-
# their subclasses, we have to build the hierarchy between self and
-
# the record's class.
-
1
def find_finder_class_for(record) #:nodoc:
-
518
class_hierarchy = [record.class]
-
-
518
while class_hierarchy.first != @klass
-
class_hierarchy.unshift(class_hierarchy.first.superclass)
-
end
-
-
1036
class_hierarchy.detect { |klass| !klass.abstract_class? }
-
end
-
-
1
def build_relation(klass, table, attribute, value) #:nodoc:
-
518
if reflection = klass.reflect_on_association(attribute)
-
attribute = reflection.foreign_key
-
value = value.attributes[reflection.primary_key_column.name] unless value.nil?
-
end
-
-
518
attribute_name = attribute.to_s
-
-
# the attribute may be an aliased attribute
-
518
if klass.attribute_aliases[attribute_name]
-
attribute = klass.attribute_aliases[attribute_name]
-
attribute_name = attribute.to_s
-
end
-
-
518
column = klass.columns_hash[attribute_name]
-
518
value = klass.connection.type_cast(value, column)
-
518
value = value.to_s[0, column.limit] if value && column.limit && column.text?
-
-
518
if !options[:case_sensitive] && value && column.text?
-
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
-
518
klass.connection.case_insensitive_comparison(table, attribute, column, value)
-
else
-
value = klass.connection.case_sensitive_modifier(value) unless value.nil?
-
table[attribute].eq(value)
-
end
-
end
-
-
1
def scope_relation(record, table, relation)
-
518
Array(options[:scope]).each do |scope_item|
-
if reflection = record.class.reflect_on_association(scope_item)
-
scope_value = record.send(reflection.foreign_key)
-
scope_item = reflection.foreign_key
-
else
-
scope_value = record.read_attribute(scope_item)
-
end
-
relation = relation.and(table[scope_item].eq(scope_value))
-
end
-
-
518
relation
-
end
-
-
1
def deserialize_attribute(record, attribute, value)
-
518
coder = record.class.serialized_attributes[attribute.to_s]
-
518
value = coder.dump value if value && coder
-
518
value
-
end
-
end
-
-
1
module ClassMethods
-
# Validates whether the value of the specified attributes are unique
-
# across the system. Useful for making sure that only one user
-
# can be named "davidhh".
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name
-
# end
-
#
-
# It can also validate whether the value of the specified attributes are
-
# unique based on a <tt>:scope</tt> parameter:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name, scope: :account_id
-
# end
-
#
-
# Or even multiple scope parameters. For example, making sure that a
-
# teacher can only be on the schedule once per semester for a particular
-
# class.
-
#
-
# class TeacherSchedule < ActiveRecord::Base
-
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
-
# end
-
#
-
# It is also possible to limit the uniqueness constraint to a set of
-
# records matching certain conditions. In this example archived articles
-
# are not being taken into consideration when validating uniqueness
-
# of the title attribute:
-
#
-
# class Article < ActiveRecord::Base
-
# validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
-
# end
-
#
-
# When the record is created, a check is performed to make sure that no
-
# record exists in the database with the given value for the specified
-
# attribute (that maps to a column). When the record is updated,
-
# the same check is made but disregarding the record itself.
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - Specifies a custom error message (default is:
-
# "has already been taken").
-
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
-
# the uniqueness constraint.
-
# * <tt>:conditions</tt> - Specify the conditions to be included as a
-
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
-
# (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
-
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
-
# non-text columns (+true+ by default).
-
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
-
# attribute is blank (default is +false+).
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should ot occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
#
-
# === Concurrency and integrity
-
#
-
# Using this validation method in conjunction with ActiveRecord::Base#save
-
# does not guarantee the absence of duplicate record insertions, because
-
# uniqueness checks on the application level are inherently prone to race
-
# conditions. For example, suppose that two users try to post a Comment at
-
# the same time, and a Comment's title must be unique. At the database-level,
-
# the actions performed by these users could be interleaved in the following manner:
-
#
-
# User 1 | User 2
-
# ------------------------------------+--------------------------------------
-
# # User 1 checks whether there's |
-
# # already a comment with the title |
-
# # 'My Post'. This is not the case. |
-
# SELECT * FROM comments |
-
# WHERE title = 'My Post' |
-
# |
-
# | # User 2 does the same thing and also
-
# | # infers that their title is unique.
-
# | SELECT * FROM comments
-
# | WHERE title = 'My Post'
-
# |
-
# # User 1 inserts their comment. |
-
# INSERT INTO comments |
-
# (title, content) VALUES |
-
# ('My Post', 'hi!') |
-
# |
-
# | # User 2 does the same thing.
-
# | INSERT INTO comments
-
# | (title, content) VALUES
-
# | ('My Post', 'hello!')
-
# |
-
# | # ^^^^^^
-
# | # Boom! We now have a duplicate
-
# | # title!
-
#
-
# This could even happen if you use transactions with the 'serializable'
-
# isolation level. The best way to work around this problem is to add a unique
-
# index to the database table using
-
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
-
# rare case that a race condition occurs, the database will guarantee
-
# the field's uniqueness.
-
#
-
# When the database catches such a duplicate insertion,
-
# ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
-
# exception. You can either choose to let this error propagate (which
-
# will result in the default Rails exception page being shown), or you
-
# can catch it and restart the transaction (e.g. by telling the user
-
# that the title already exists, and asking them to re-enter the title).
-
# This technique is also known as
-
# {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control].
-
#
-
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
-
# constraint errors from other types of database errors by throwing an
-
# ActiveRecord::RecordNotUnique exception. For other adapters you will
-
# have to parse the (database-specific) exception message to detect such
-
# a case.
-
#
-
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
-
#
-
# * ActiveRecord::ConnectionAdapters::MysqlAdapter.
-
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
-
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
-
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
-
1
def validates_uniqueness_of(*attr_names)
-
validates_with UniquenessValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require 'active_record/associations/builder/association'
-
1
require 'active_support/core_ext/module/aliasing'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord::Associations::Builder
-
1
class DeprecatedOptionsProc
-
1
attr_reader :options
-
-
1
def initialize(options)
-
options[:includes] = options.delete(:include) if options[:include]
-
options[:where] = options.delete(:conditions) if options[:conditions]
-
-
@options = options
-
end
-
-
1
def to_proc
-
options = self.options
-
proc do |owner|
-
if options[:where].respond_to?(:to_proc)
-
context = owner || self
-
where(context.instance_eval(&options[:where]))
-
.merge!(options.except(:where))
-
else
-
merge(options)
-
end
-
end
-
end
-
-
1
def arity
-
1
-
end
-
end
-
-
1
class Association
-
1
DEPRECATED_OPTIONS = [:readonly, :order, :limit, :group, :having,
-
:offset, :select, :uniq, :include, :conditions]
-
-
1
self.valid_options += [:select, :conditions, :include, :readonly]
-
-
1
def initialize_with_deprecated_options(model, name, scope, options)
-
14
options = scope if scope.is_a?(Hash)
-
14
deprecated_options = options.slice(*DEPRECATED_OPTIONS)
-
-
14
if scope.respond_to?(:call) && !deprecated_options.empty?
-
raise ArgumentError,
-
"Invalid mix of scope block and deprecated finder options on " \
-
"ActiveRecord association: #{model.name}.#{macro} :#{name}"
-
end
-
-
14
if scope.is_a?(Hash)
-
13
if deprecated_options.empty?
-
13
scope = nil
-
else
-
ActiveSupport::Deprecation.warn(
-
"The following options in your #{model.name}.#{macro} :#{name} declaration are deprecated: " \
-
"#{deprecated_options.keys.map(&:inspect).join(',')}. Please use a scope block instead. " \
-
"For example, the following:\n" \
-
"\n" \
-
" has_many :spam_comments, conditions: { spam: true }, class_name: 'Comment'\n" \
-
"\n" \
-
"should be rewritten as the following:\n" \
-
"\n" \
-
" has_many :spam_comments, -> { where spam: true }, class_name: 'Comment'\n"
-
)
-
scope = DeprecatedOptionsProc.new(deprecated_options)
-
options = options.except(*DEPRECATED_OPTIONS)
-
end
-
end
-
-
14
initialize_without_deprecated_options(model, name, scope, options)
-
end
-
-
1
alias_method_chain :initialize, :deprecated_options
-
end
-
-
1
class CollectionAssociation
-
1
include Module.new {
-
1
def valid_options
-
8
super + [:order, :group, :having, :limit, :offset, :uniq]
-
end
-
}
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
1
module DeprecatedFinders
-
1
class ScopeWrapper
-
1
def self.wrap(klass, scope)
-
3
if scope.is_a?(Hash)
-
ActiveSupport::Deprecation.warn(
-
"Calling #scope or #default_scope with a hash is deprecated. Please use a lambda " \
-
"containing a scope. E.g. scope :red, -> { where(color: 'red') }"
-
)
-
-
new(klass, scope)
-
3
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
-
3
new(klass, scope)
-
else
-
scope
-
end
-
end
-
-
1
def initialize(klass, scope)
-
3
@klass = klass
-
3
@scope = scope
-
end
-
-
1
def call(*args)
-
598
if @scope.respond_to?(:call)
-
598
result = @scope.call(*args)
-
-
598
if result.is_a?(Hash)
-
msg = "Returning a hash from a #scope or #default_scope block is deprecated. Please " \
-
"return an actual scope object instead. E.g. scope :red, -> { where(color: 'red') } " \
-
"rather than scope :red, -> { { conditions: { color: 'red' } } }. "
-
-
if @scope.respond_to?(:source_location)
-
msg << "(The scope was defined at #{@scope.source_location.join(':')}.)"
-
end
-
-
ActiveSupport::Deprecation.warn(msg)
-
end
-
else
-
result = @scope
-
end
-
-
598
if result.is_a?(Hash)
-
@klass.all.apply_finder_options(result, true)
-
else
-
598
result
-
end
-
end
-
end
-
-
1
def default_scope(scope = {}, &block)
-
2
if block_given?
-
super ScopeWrapper.new(self, block), &nil
-
else
-
2
super ScopeWrapper.wrap(self, scope)
-
end
-
end
-
-
1
def scoped(options = nil)
-
ActiveSupport::Deprecation.warn("Model.scoped is deprecated. Please use Model.all instead.")
-
options ? all.apply_finder_options(options, true) : all
-
end
-
-
1
def all(options = nil)
-
2772
options ? super().all(options) : super()
-
end
-
-
1
def scope(name, body = {}, &block)
-
1
super(name, ScopeWrapper.wrap(self, body), &block)
-
end
-
-
1
def with_scope(scope = {}, action = :merge)
-
ActiveSupport::Deprecation.warn(
-
"ActiveRecord::Base#with_scope and #with_exclusive_scope are deprecated. " \
-
"Please use ActiveRecord::Relation#scoping instead. (You can use #merge " \
-
"to merge multiple scopes together.)"
-
)
-
-
# If another Active Record class has been passed in, get its current scope
-
scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
-
-
previous_scope = self.current_scope
-
-
if scope.is_a?(Hash)
-
# Dup first and second level of hash (method and params).
-
scope = scope.dup
-
scope.each do |method, params|
-
scope[method] = params.dup unless params == true
-
end
-
-
scope.assert_valid_keys([ :find, :create ])
-
relation = construct_finder_arel(scope[:find] || {})
-
-
if relation.respond_to?(:default_scoped=)
-
relation.default_scoped = true unless action == :overwrite
-
end
-
-
if previous_scope && previous_scope.create_with_value && scope[:create]
-
scope_for_create = if action == :merge
-
previous_scope.create_with_value.merge(scope[:create])
-
else
-
scope[:create]
-
end
-
-
relation = relation.create_with(scope_for_create)
-
else
-
scope_for_create = scope[:create]
-
scope_for_create ||= previous_scope.create_with_value if previous_scope
-
relation = relation.create_with(scope_for_create) if scope_for_create
-
end
-
-
scope = relation
-
end
-
-
scope = previous_scope.merge(scope) if previous_scope && action == :merge
-
scope.scoping { yield }
-
end
-
-
1
protected
-
-
# Works like with_scope, but discards any nested properties.
-
1
def with_exclusive_scope(method_scoping = {}, &block)
-
if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
-
raise ArgumentError, <<-MSG
-
New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
-
-
User.unscoped.where(:active => true)
-
-
Or call unscoped with a block:
-
-
User.unscoped do
-
User.where(:active => true).all
-
end
-
-
MSG
-
end
-
with_scope(method_scoping, :overwrite, &block)
-
end
-
-
1
private
-
-
1
def construct_finder_arel(options = {}, scope = nil)
-
relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options, true) : options
-
relation = scope.merge(relation) if scope
-
relation
-
end
-
end
-
-
1
class Base
-
1
extend DeprecatedFinders
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class CollectionProxy
-
1
module InterceptDynamicInstantiators
-
1
def method_missing(method, *args, &block)
-
2
match = DynamicMatchers::Method.match(klass, method)
-
-
2
if match && match.is_a?(DynamicMatchers::Instantiator)
-
scoping do
-
klass.send(method, *args) do |record|
-
-
sanitized_method = match.class.prefix + match.class.suffix
-
if %w(find_or_create_by find_or_create_by!).include?(sanitized_method) && proxy_association.reflection.options[:through].present?
-
proxy_association.send(:save_through_record, record)
-
else
-
proxy_association.add_to_target(record)
-
end
-
yield record if block_given?
-
end
-
end
-
else
-
2
super
-
end
-
end
-
end
-
-
1
def self.inherited(subclass)
-
4
subclass.class_eval do
-
# Ensure this get included first
-
4
include ActiveRecord::Delegation::ClassSpecificRelation
-
-
unless ActiveRecord::VERSION::MAJOR == 4 &&
-
4
ActiveRecord::VERSION::MINOR == 2 &&
-
ActiveRecord::VERSION::TINY > 1
-
# Now override the method_missing definition
-
4
include InterceptDynamicInstantiators
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
1
module DynamicMatchers
-
1
module DeprecatedFinder
-
1
def body
-
<<-CODE
-
result = #{super}
-
result && block_given? ? yield(result) : result
-
CODE
-
end
-
-
1
def result
-
"all.apply_finder_options(options, true).#{super}"
-
end
-
-
1
def signature
-
"#{super}, options = {}"
-
end
-
end
-
-
1
module DeprecationWarning
-
1
def body
-
"#{deprecation_warning}\n#{super}"
-
end
-
-
1
def deprecation_warning
-
%{ActiveSupport::Deprecation.warn("This dynamic method is deprecated. Please use e.g. #{deprecation_alternative} instead.")}
-
end
-
end
-
-
1
module FindByDeprecationWarning
-
1
def body
-
<<-CODE
-
if block_given?
-
ActiveSupport::Deprecation.warn("Calling find_by or find_by! methods with a block is deprecated with no replacement.")
-
end
-
-
unless options.empty?
-
ActiveSupport::Deprecation.warn(
-
"Calling find_by or find_by! methods with options is deprecated. " \
-
"Build a scope instead, e.g. User.where('age > 21').find_by_name('Jon')."
-
)
-
end
-
-
#{super}
-
CODE
-
end
-
end
-
-
1
class FindBy
-
1
include DeprecatedFinder
-
1
include FindByDeprecationWarning
-
end
-
-
1
class FindByBang
-
1
include DeprecatedFinder
-
1
include FindByDeprecationWarning
-
end
-
-
1
class FindAllBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
1
include DeprecatedFinder
-
1
include DeprecationWarning
-
-
1
def self.prefix
-
1
"find_all_by"
-
end
-
-
1
def finder
-
"where"
-
end
-
-
1
def result
-
"#{super}.to_a"
-
end
-
-
1
def deprecation_alternative
-
"Post.where(...).all"
-
end
-
end
-
-
1
class FindLastBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
1
include DeprecatedFinder
-
1
include DeprecationWarning
-
-
1
def self.prefix
-
1
"find_last_by"
-
end
-
-
1
def finder
-
"where"
-
end
-
-
1
def result
-
"#{super}.last"
-
end
-
-
1
def deprecation_alternative
-
"Post.where(...).last"
-
end
-
end
-
-
1
class ScopedBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
1
include DeprecationWarning
-
-
1
def self.prefix
-
1
"scoped_by"
-
end
-
-
1
def body
-
"#{deprecation_warning} \n where(#{attributes_hash})"
-
end
-
-
1
def deprecation_alternative
-
"Post.where(...)"
-
end
-
end
-
-
1
class Instantiator < Method
-
1
include DeprecationWarning
-
-
1
def self.dispatch(klass, attribute_names, instantiator, args, block)
-
if args.length == 1 && args.first.is_a?(Hash)
-
attributes = args.first.stringify_keys
-
conditions = attributes.slice(*attribute_names)
-
rest = [attributes.except(*attribute_names)]
-
else
-
raise ArgumentError, "too few arguments" unless args.length >= attribute_names.length
-
-
conditions = Hash[attribute_names.map.with_index { |n, i| [n, args[i]] }]
-
rest = args.drop(attribute_names.length)
-
end
-
-
klass.where(conditions).first ||
-
klass.create_with(conditions).send(instantiator, *rest, &block)
-
end
-
-
1
def signature
-
"*args, &block"
-
end
-
-
1
def body
-
<<-CODE
-
#{deprecation_warning}
-
#{self.class}.dispatch(self, #{attribute_names.inspect}, #{instantiator.inspect}, args, block)
-
CODE
-
end
-
-
1
def instantiator
-
raise NotImplementedError
-
end
-
-
1
def deprecation_alternative
-
"Post.#{self.class.prefix}#{self.class.suffix}(name: 'foo')"
-
end
-
end
-
-
1
class FindOrInitializeBy < Instantiator
-
1
Method.matchers << self
-
-
1
def self.prefix
-
1
"find_or_initialize_by"
-
end
-
-
1
def instantiator
-
"new"
-
end
-
end
-
-
1
class FindOrCreateBy < Instantiator
-
1
Method.matchers << self
-
-
1
def self.prefix
-
1
"find_or_create_by"
-
end
-
-
1
def instantiator
-
"create"
-
end
-
end
-
-
1
class FindOrCreateByBang < Instantiator
-
1
Method.matchers << self
-
-
1
def self.prefix
-
1
"find_or_create_by"
-
end
-
-
1
def self.suffix
-
1
"!"
-
end
-
-
1
def instantiator
-
"create!"
-
end
-
end
-
end
-
end
-
1
require 'active_record/relation'
-
1
require 'active_support/core_ext/module/aliasing'
-
-
1
module ActiveRecord
-
1
class Relation
-
1
module DeprecatedMethods
-
1
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
-
:order, :select, :readonly, :group, :having, :from, :lock ]
-
-
# The silence_deprecation arg is for internal use, where we have already output a
-
# deprecation further up the call stack.
-
1
def apply_finder_options(options, silence_deprecation = false)
-
ActiveSupport::Deprecation.warn("#apply_finder_options is deprecated") unless silence_deprecation
-
-
relation = clone
-
return relation unless options
-
-
options.assert_valid_keys(VALID_FIND_OPTIONS)
-
finders = options.dup
-
finders.delete_if { |key, value| value.nil? && key != :limit }
-
-
((VALID_FIND_OPTIONS - [:conditions, :include]) & finders.keys).each do |finder|
-
relation = relation.send(finder, finders[finder])
-
end
-
-
relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
-
relation = relation.includes(finders[:include]) if options.has_key?(:include)
-
-
relation
-
end
-
-
1
def update_all_with_deprecated_options(updates, conditions = nil, options = {})
-
scope = self
-
-
if conditions
-
scope = where(conditions)
-
-
ActiveSupport::Deprecation.warn(
-
"Relation#update_all with conditions is deprecated. Please use " \
-
"Item.where(color: 'red').update_all(...) rather than " \
-
"Item.update_all(..., color: 'red').", caller
-
)
-
end
-
-
if options.present?
-
scope = scope.apply_finder_options(options.slice(:limit, :order), true)
-
-
ActiveSupport::Deprecation.warn(
-
"Relation#update_all with :limit / :order options is deprecated. " \
-
"Please use e.g. Post.limit(1).order(:foo).update_all instead.", caller
-
)
-
end
-
-
scope.update_all_without_deprecated_options(updates)
-
end
-
-
1
def find_in_batches(options = {}, &block)
-
if (finder_options = options.except(:start, :batch_size)).present?
-
ActiveSupport::Deprecation.warn(
-
"Relation#find_in_batches with finder options is deprecated. Please build " \
-
"a scope and then call find_in_batches on it instead.", caller
-
)
-
-
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
-
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
-
-
apply_finder_options(finder_options, true).
-
find_in_batches(options.slice(:start, :batch_size), &block)
-
else
-
super
-
end
-
end
-
-
1
def calculate(operation, column_name, options = {})
-
610
if options.except(:distinct).present?
-
ActiveSupport::Deprecation.warn(
-
"Relation#calculate with finder options is deprecated. Please build " \
-
"a scope and then call calculate on it instead.", caller
-
)
-
-
apply_finder_options(options.except(:distinct), true)
-
.calculate(operation, column_name, options.slice(:distinct))
-
else
-
610
super
-
end
-
end
-
-
1
def find(*args)
-
74
options = args.extract_options!
-
-
74
if options.present?
-
scope = apply_finder_options(options, true)
-
-
case finder = args.first
-
when :first, :last, :all
-
ActiveSupport::Deprecation.warn(
-
"Calling #find(#{finder.inspect}) is deprecated. Please call " \
-
"##{finder} directly instead. You have also used finder options. " \
-
"These are also deprecated. Please build a scope instead of using " \
-
"finder options.", caller
-
)
-
-
scope.send(finder)
-
else
-
ActiveSupport::Deprecation.warn(
-
"Passing options to #find is deprecated. Please build a scope " \
-
"and then call #find on it.", caller
-
)
-
-
scope.find(*args)
-
end
-
else
-
74
case finder = args.first
-
when :first, :last, :all
-
ActiveSupport::Deprecation.warn(
-
"Calling #find(#{finder.inspect}) is deprecated. Please call " \
-
"##{finder} directly instead.", caller
-
)
-
-
send(finder)
-
else
-
74
super
-
end
-
end
-
end
-
-
1
def first(*args)
-
1
if args.empty?
-
1
super
-
else
-
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
-
super
-
else
-
ActiveSupport::Deprecation.warn(
-
"Relation#first with finder options is deprecated. Please build " \
-
"a scope and then call #first on it instead.", caller
-
)
-
-
apply_finder_options(args.first, true).first
-
end
-
end
-
end
-
-
1
def last(*args)
-
if args.empty?
-
super
-
else
-
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
-
super
-
else
-
ActiveSupport::Deprecation.warn(
-
"Relation#last with finder options is deprecated. Please build " \
-
"a scope and then call #last on it instead.", caller
-
)
-
-
apply_finder_options(args.first, true).last
-
end
-
end
-
end
-
-
1
def all(*args)
-
ActiveSupport::Deprecation.warn(
-
"Relation#all is deprecated. If you want to eager-load a relation, you can " \
-
"call #load (e.g. `Post.where(published: true).load`). If you want " \
-
"to get an array of records from a relation, you can call #to_a (e.g. " \
-
"`Post.where(published: true).to_a`).", caller
-
)
-
apply_finder_options(args.first, true).to_a
-
end
-
end
-
-
1
include DeprecatedMethods
-
1
alias_method_chain :update_all, :deprecated_options
-
end
-
end
-
1
module ActiveSupport
-
# Backtraces often include many lines that are not relevant for the context
-
# under review. This makes it hard to find the signal amongst the backtrace
-
# noise, and adds debugging time. With a BacktraceCleaner, filters and
-
# silencers are used to remove the noisy lines, so that only the most relevant
-
# lines remain.
-
#
-
# Filters are used to modify lines of data, while silencers are used to remove
-
# lines entirely. The typical filter use case is to remove lengthy path
-
# information from the start of each line, and view file paths relevant to the
-
# app directory instead of the file system root. The typical silencer use case
-
# is to exclude the output of a noisy library from the backtrace, so that you
-
# can focus on the rest.
-
#
-
# bc = BacktraceCleaner.new
-
# bc.add_filter { |line| line.gsub(Rails.root, '') }
-
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
-
# bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems
-
#
-
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
-
# and show as much data as possible, you can always call
-
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
-
# backtrace to a pristine state. If you need to reconfigure an existing
-
# BacktraceCleaner so that it does not filter or modify the paths of any lines
-
# of the backtrace, you can call BacktraceCleaner#remove_filters! These two
-
# methods will give you a completely untouched backtrace.
-
#
-
# Inspired by the Quiet Backtrace gem by Thoughtbot.
-
1
class BacktraceCleaner
-
1
def initialize
-
1
@filters, @silencers = [], []
-
end
-
-
# Returns the backtrace after all filters and silencers have been run
-
# against it. Filters run first, then silencers.
-
1
def clean(backtrace, kind = :silent)
-
filtered = filter_backtrace(backtrace)
-
-
case kind
-
when :silent
-
silence(filtered)
-
when :noise
-
noise(filtered)
-
else
-
filtered
-
end
-
end
-
1
alias :filter :clean
-
-
# Adds a filter from the block provided. Each line in the backtrace will be
-
# mapped against this filter.
-
#
-
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
-
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
-
1
def add_filter(&block)
-
4
@filters << block
-
end
-
-
# Adds a silencer from the block provided. If the silencer returns +true+
-
# for a given line, it will be excluded from the clean backtrace.
-
#
-
# # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
-
# backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
-
1
def add_silencer(&block)
-
1
@silencers << block
-
end
-
-
# Will remove all silencers, but leave in the filters. This is useful if
-
# your context of debugging suddenly expands as you suspect a bug in one of
-
# the libraries you use.
-
1
def remove_silencers!
-
@silencers = []
-
end
-
-
# Removes all filters, but leaves in silencers. Useful if you suddenly
-
# need to see entire filepaths in the backtrace that you had already
-
# filtered out.
-
1
def remove_filters!
-
@filters = []
-
end
-
-
1
private
-
1
def filter_backtrace(backtrace)
-
@filters.each do |f|
-
backtrace = backtrace.map { |line| f.call(line) }
-
end
-
-
backtrace
-
end
-
-
1
def silence(backtrace)
-
@silencers.each do |s|
-
backtrace = backtrace.reject { |line| s.call(line) }
-
end
-
-
backtrace
-
end
-
-
1
def noise(backtrace)
-
backtrace - silence(backtrace)
-
end
-
end
-
end
-
1
require 'openssl'
-
1
require 'base64'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActiveSupport
-
# MessageEncryptor is a simple way to encrypt values which get stored
-
# somewhere you don't trust.
-
#
-
# The cipher text and initialization vector are base64 encoded and returned
-
# to you.
-
#
-
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
-
# where you don't want users to be able to determine the value of the payload.
-
#
-
# salt = SecureRandom.random_bytes(64)
-
# key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
-
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
-
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
-
# crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
-
1
class MessageEncryptor
-
1
module NullSerializer #:nodoc:
-
1
def self.load(value)
-
171
value
-
end
-
-
1
def self.dump(value)
-
239
value
-
end
-
end
-
-
1
class InvalidMessage < StandardError; end
-
1
OpenSSLCipherError = OpenSSL::Cipher::CipherError
-
-
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
-
# the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
-
# bits. If you are using a user-entered secret, you can generate a suitable
-
# key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or
-
# similar.
-
#
-
# Options:
-
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
-
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
-
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
-
1
def initialize(secret, *signature_key_or_options)
-
352
options = signature_key_or_options.extract_options!
-
352
sign_secret = signature_key_or_options.first
-
352
@secret = secret
-
352
@sign_secret = sign_secret
-
352
@cipher = options[:cipher] || 'aes-256-cbc'
-
352
@verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
-
352
@serializer = options[:serializer] || Marshal
-
end
-
-
# Encrypt and sign a message. We need to sign the message in order to avoid
-
# padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
-
1
def encrypt_and_sign(value)
-
239
verifier.generate(_encrypt(value))
-
end
-
-
# Decrypt and verify a message. We need to verify the message in order to
-
# avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
-
1
def decrypt_and_verify(value)
-
171
_decrypt(verifier.verify(value))
-
end
-
-
1
private
-
-
1
def _encrypt(value)
-
239
cipher = new_cipher
-
239
cipher.encrypt
-
239
cipher.key = @secret
-
-
# Rely on OpenSSL for the initialization vector
-
239
iv = cipher.random_iv
-
-
239
encrypted_data = cipher.update(@serializer.dump(value))
-
239
encrypted_data << cipher.final
-
-
717
[encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
-
end
-
-
1
def _decrypt(encrypted_message)
-
171
cipher = new_cipher
-
513
encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.decode64(v)}
-
-
171
cipher.decrypt
-
171
cipher.key = @secret
-
171
cipher.iv = iv
-
-
171
decrypted_data = cipher.update(encrypted_data)
-
171
decrypted_data << cipher.final
-
-
171
@serializer.load(decrypted_data)
-
rescue OpenSSLCipherError, TypeError
-
raise InvalidMessage
-
end
-
-
1
def new_cipher
-
410
OpenSSL::Cipher::Cipher.new(@cipher)
-
end
-
-
1
def verifier
-
410
@verifier
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'active_support/json'
-
1
require 'active_support/core_ext/string/access'
-
1
require 'active_support/core_ext/string/behavior'
-
1
require 'active_support/core_ext/module/delegation'
-
-
1
module ActiveSupport #:nodoc:
-
1
module Multibyte #:nodoc:
-
# Chars enables you to work transparently with UTF-8 encoding in the Ruby
-
# String class without having extensive knowledge about the encoding. A
-
# Chars object accepts a string upon initialization and proxies String
-
# methods in an encoding safe manner. All the normal String methods are also
-
# implemented on the proxy.
-
#
-
# String methods are proxied through the Chars object, and can be accessed
-
# through the +mb_chars+ method. Methods which would normally return a
-
# String object now return a Chars object so methods can be chained.
-
#
-
# 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
-
#
-
# Chars objects are perfectly interchangeable with String objects as long as
-
# no explicit class checks are made. If certain methods do explicitly check
-
# the class, call +to_s+ before you pass chars objects to them.
-
#
-
# bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
-
#
-
# The default Chars implementation assumes that the encoding of the string
-
# is UTF-8, if you want to handle different encodings you can write your own
-
# multibyte string handler and configure it through
-
# ActiveSupport::Multibyte.proxy_class.
-
#
-
# class CharsForUTF32
-
# def size
-
# @wrapped_string.size / 4
-
# end
-
#
-
# def self.accepts?(string)
-
# string.length % 4 == 0
-
# end
-
# end
-
#
-
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
-
1
class Chars
-
1
include Comparable
-
1
attr_reader :wrapped_string
-
1
alias to_s wrapped_string
-
1
alias to_str wrapped_string
-
-
1
delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
-
-
# Creates a new Chars instance by wrapping _string_.
-
1
def initialize(string)
-
@wrapped_string = string
-
@wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
-
end
-
-
# Forward all undefined methods to the wrapped string.
-
1
def method_missing(method, *args, &block)
-
if method.to_s =~ /!$/
-
result = @wrapped_string.__send__(method, *args, &block)
-
self if result
-
else
-
result = @wrapped_string.__send__(method, *args, &block)
-
result.kind_of?(String) ? chars(result) : result
-
end
-
end
-
-
# Returns +true+ if _obj_ responds to the given method. Private methods
-
# are included in the search only if the optional second parameter
-
# evaluates to +true+.
-
1
def respond_to_missing?(method, include_private)
-
@wrapped_string.respond_to?(method, include_private)
-
end
-
-
# Returns +true+ when the proxy class can handle the string. Returns
-
# +false+ otherwise.
-
1
def self.consumes?(string)
-
string.encoding == Encoding::UTF_8
-
end
-
-
# Works just like <tt>String#split</tt>, with the exception that the items
-
# in the resulting list are Chars instances instead of String. This makes
-
# chaining methods easier.
-
#
-
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
-
1
def split(*args)
-
@wrapped_string.split(*args).map { |i| self.class.new(i) }
-
end
-
-
# Works like like <tt>String#slice!</tt>, but returns an instance of
-
# Chars, or nil if the string was not modified.
-
1
def slice!(*args)
-
chars(@wrapped_string.slice!(*args))
-
end
-
-
# Reverses all characters in the string.
-
#
-
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
-
1
def reverse
-
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
-
end
-
-
# Limits the byte size of the string to a number of bytes without breaking
-
# characters. Usable when the storage for a string is limited for some
-
# reason.
-
#
-
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
-
1
def limit(limit)
-
slice(0...translate_offset(limit))
-
end
-
-
# Converts characters in the string to uppercase.
-
#
-
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
-
1
def upcase
-
chars Unicode.upcase(@wrapped_string)
-
end
-
-
# Converts characters in the string to lowercase.
-
#
-
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
-
1
def downcase
-
chars Unicode.downcase(@wrapped_string)
-
end
-
-
# Converts characters in the string to the opposite case.
-
#
-
# 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
-
1
def swapcase
-
chars Unicode.swapcase(@wrapped_string)
-
end
-
-
# Converts the first character to uppercase and the remainder to lowercase.
-
#
-
# 'über'.mb_chars.capitalize.to_s # => "Über"
-
1
def capitalize
-
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
-
end
-
-
# Capitalizes the first letter of every word, when possible.
-
#
-
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
-
# "日本語".mb_chars.titleize # => "日本語"
-
1
def titleize
-
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
-
end
-
1
alias_method :titlecase, :titleize
-
-
# Returns the KC normalization of the string by default. NFKC is
-
# considered the best normalization form for passing strings to databases
-
# and validations.
-
#
-
# * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
-
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
-
# ActiveSupport::Multibyte::Unicode.default_normalization_form
-
1
def normalize(form = nil)
-
chars(Unicode.normalize(@wrapped_string, form))
-
end
-
-
# Performs canonical decomposition on all the characters.
-
#
-
# 'é'.length # => 2
-
# 'é'.mb_chars.decompose.to_s.length # => 3
-
1
def decompose
-
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
-
end
-
-
# Performs composition on all the characters.
-
#
-
# 'é'.length # => 3
-
# 'é'.mb_chars.compose.to_s.length # => 2
-
1
def compose
-
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
-
end
-
-
# Returns the number of grapheme clusters in the string.
-
#
-
# 'क्षि'.mb_chars.length # => 4
-
# 'क्षि'.mb_chars.grapheme_length # => 3
-
1
def grapheme_length
-
Unicode.unpack_graphemes(@wrapped_string).length
-
end
-
-
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
-
# resulting in a valid UTF-8 string.
-
#
-
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
-
# encoding is entirely CP1252 or ISO-8859-1.
-
1
def tidy_bytes(force = false)
-
chars(Unicode.tidy_bytes(@wrapped_string, force))
-
end
-
-
1
def as_json(options = nil) #:nodoc:
-
to_s.as_json(options)
-
end
-
-
1
%w(capitalize downcase reverse tidy_bytes upcase).each do |method|
-
5
define_method("#{method}!") do |*args|
-
@wrapped_string = send(method, *args).to_s
-
self
-
end
-
end
-
-
1
protected
-
-
1
def translate_offset(byte_offset) #:nodoc:
-
return nil if byte_offset.nil?
-
return 0 if @wrapped_string == ''
-
-
begin
-
@wrapped_string.byteslice(0...byte_offset).unpack('U*').length
-
rescue ArgumentError
-
byte_offset -= 1
-
retry
-
end
-
end
-
-
1
def chars(string) #:nodoc:
-
self.class.new(string)
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
module BindVisitor
-
1
def initialize target
-
@block = nil
-
super
-
end
-
-
1
def accept node, &block
-
@block = block if block_given?
-
super
-
end
-
-
1
private
-
-
1
def visit_Arel_Nodes_Assignment o, a
-
if o.right.is_a? Arel::Nodes::BindParam
-
"#{visit o.left, a} = #{visit o.right, a}"
-
else
-
super
-
end
-
end
-
-
1
def visit_Arel_Nodes_BindParam o, a
-
if @block
-
@block.call
-
else
-
super
-
end
-
end
-
-
end
-
end
-
end
-
# A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
-
# hashing passwords.
-
1
module BCrypt
-
end
-
-
1
if RUBY_PLATFORM == "java"
-
require 'java'
-
else
-
1
require "openssl"
-
end
-
-
1
begin
-
1
RUBY_VERSION =~ /(\d+.\d+)/
-
1
require "#{$1}/bcrypt_ext"
-
rescue LoadError
-
1
require "bcrypt_ext"
-
end
-
-
1
require 'bcrypt/error'
-
1
require 'bcrypt/engine'
-
1
require 'bcrypt/password'
-
1
module BCrypt
-
# A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
-
1
class Engine
-
# The default computational expense parameter.
-
1
DEFAULT_COST = 10
-
# The minimum cost supported by the algorithm.
-
1
MIN_COST = 4
-
# Maximum possible size of bcrypt() salts.
-
1
MAX_SALT_LENGTH = 16
-
-
1
if RUBY_PLATFORM != "java"
-
# C-level routines which, if they don't get the right input, will crash the
-
# hell out of the Ruby process.
-
1
private_class_method :__bc_salt
-
1
private_class_method :__bc_crypt
-
end
-
-
1
@cost = nil
-
-
# Returns the cost factor that will be used if one is not specified when
-
# creating a password hash. Defaults to DEFAULT_COST if not set.
-
1
def self.cost
-
@cost || DEFAULT_COST
-
end
-
-
# Set a default cost factor that will be used if one is not specified when
-
# creating a password hash.
-
#
-
# Example:
-
#
-
# BCrypt::Engine::DEFAULT_COST #=> 10
-
# BCrypt::Password.create('secret').cost #=> 10
-
#
-
# BCrypt::Engine.cost = 8
-
# BCrypt::Password.create('secret').cost #=> 8
-
#
-
# # cost can still be overridden as needed
-
# BCrypt::Password.create('secret', :cost => 6).cost #=> 6
-
1
def self.cost=(cost)
-
@cost = cost
-
end
-
-
# Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
-
# a bcrypt() password hash.
-
1
def self.hash_secret(secret, salt, cost = nil)
-
298
if valid_secret?(secret)
-
298
if valid_salt?(salt)
-
298
if cost.nil?
-
58
cost = autodetect_cost(salt)
-
end
-
-
298
if RUBY_PLATFORM == "java"
-
Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
-
else
-
298
__bc_crypt(secret.to_s, salt)
-
end
-
else
-
raise Errors::InvalidSalt.new("invalid salt")
-
end
-
else
-
raise Errors::InvalidSecret.new("invalid secret")
-
end
-
end
-
-
# Generates a random salt with a given computational cost.
-
1
def self.generate_salt(cost = self.cost)
-
240
cost = cost.to_i
-
240
if cost > 0
-
240
if cost < MIN_COST
-
cost = MIN_COST
-
end
-
240
if RUBY_PLATFORM == "java"
-
Java.bcrypt_jruby.BCrypt.gensalt(cost)
-
else
-
240
prefix = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
-
240
__bc_salt(prefix, cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
-
end
-
else
-
raise Errors::InvalidCost.new("cost must be numeric and > 0")
-
end
-
end
-
-
# Returns true if +salt+ is a valid bcrypt() salt, false if not.
-
1
def self.valid_salt?(salt)
-
298
!!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/)
-
end
-
-
# Returns true if +secret+ is a valid bcrypt() secret, false if not.
-
1
def self.valid_secret?(secret)
-
298
secret.respond_to?(:to_s)
-
end
-
-
# Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
-
#
-
# Example:
-
#
-
# BCrypt::Engine.calibrate(200) #=> 10
-
# BCrypt::Engine.calibrate(1000) #=> 12
-
#
-
# # should take less than 200ms
-
# BCrypt::Password.create("woo", :cost => 10)
-
#
-
# # should take less than 1000ms
-
# BCrypt::Password.create("woo", :cost => 12)
-
1
def self.calibrate(upper_time_limit_in_ms)
-
40.times do |i|
-
start_time = Time.now
-
Password.create("testing testing", :cost => i+1)
-
end_time = Time.now - start_time
-
return i if end_time * 1_000 > upper_time_limit_in_ms
-
end
-
end
-
-
# Autodetects the cost from the salt string.
-
1
def self.autodetect_cost(salt)
-
58
salt[4..5].to_i
-
end
-
end
-
-
end
-
1
module BCrypt
-
-
1
class Error < StandardError # :nodoc:
-
end
-
-
1
module Errors # :nodoc:
-
-
# The salt parameter provided to bcrypt() is invalid.
-
1
class InvalidSalt < BCrypt::Error; end
-
-
# The hash parameter provided to bcrypt() is invalid.
-
1
class InvalidHash < BCrypt::Error; end
-
-
# The cost parameter provided to bcrypt() is invalid.
-
1
class InvalidCost < BCrypt::Error; end
-
-
# The secret parameter provided to bcrypt() is invalid.
-
1
class InvalidSecret < BCrypt::Error; end
-
-
end
-
-
end
-
1
module BCrypt
-
# A password management class which allows you to safely store users' passwords and compare them.
-
#
-
# Example usage:
-
#
-
# include BCrypt
-
#
-
# # hash a user's password
-
# @password = Password.create("my grand secret")
-
# @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
-
#
-
# # store it safely
-
# @user.update_attribute(:password, @password)
-
#
-
# # read it back
-
# @user.reload!
-
# @db_password = Password.new(@user.password)
-
#
-
# # compare it after retrieval
-
# @db_password == "my grand secret" #=> true
-
# @db_password == "a paltry guess" #=> false
-
#
-
1
class Password < String
-
# The hash portion of the stored password hash.
-
1
attr_reader :checksum
-
# The salt of the store password hash (including version and cost).
-
1
attr_reader :salt
-
# The version of the bcrypt() algorithm used to create the hash.
-
1
attr_reader :version
-
# The cost factor used to create the hash.
-
1
attr_reader :cost
-
-
1
class << self
-
# Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
-
# logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
-
# 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
-
# attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
-
# users' passwords.
-
#
-
# Example:
-
#
-
# @password = BCrypt::Password.create("my secret", :cost => 13)
-
1
def create(secret, options = {})
-
240
cost = options[:cost] || BCrypt::Engine.cost
-
240
raise ArgumentError if cost > 31
-
240
Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(cost), cost))
-
end
-
-
1
def valid_hash?(h)
-
298
h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
-
end
-
end
-
-
# Initializes a BCrypt::Password instance with the data from a stored hash.
-
1
def initialize(raw_hash)
-
298
if valid_hash?(raw_hash)
-
298
self.replace(raw_hash)
-
298
@version, @cost, @salt, @checksum = split_hash(self)
-
else
-
raise Errors::InvalidHash.new("invalid hash")
-
end
-
end
-
-
# Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
-
1
def ==(secret)
-
58
super(BCrypt::Engine.hash_secret(secret, @salt))
-
end
-
1
alias_method :is_password?, :==
-
-
1
private
-
-
# Returns true if +h+ is a valid hash.
-
1
def valid_hash?(h)
-
298
self.class.valid_hash?(h)
-
end
-
-
# call-seq:
-
# split_hash(raw_hash) -> version, cost, salt, hash
-
#
-
# Splits +h+ into version, cost, salt, and hash and returns them in that order.
-
1
def split_hash(h)
-
298
_, v, c, mash = h.split('$')
-
298
return v, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
-
end
-
end
-
-
end
-
1
require 'rack/utils'
-
-
1
module Rack
-
1
module Multipart
-
1
class MultipartLimitError < Errno::EMFILE; end
-
-
1
class Parser
-
1
BUFSIZE = 16384
-
-
1
def initialize(env)
-
101
@env = env
-
end
-
-
1
def parse
-
101
return nil unless setup_parse
-
-
fast_forward_to_first_boundary
-
-
opened_files = 0
-
loop do
-
-
head, filename, content_type, name, body =
-
get_current_head_and_filename_and_content_type_and_name_and_body
-
-
if Utils.multipart_part_limit > 0
-
opened_files += 1 if filename
-
raise MultipartLimitError, 'Maximum file multiparts in content reached' if opened_files >= Utils.multipart_part_limit
-
end
-
-
# Save the rest.
-
if i = @buf.index(rx)
-
body << @buf.slice!(0, i)
-
@buf.slice!(0, @boundary_size+2)
-
-
@content_length = -1 if $1 == "--"
-
end
-
-
filename, data = get_data(filename, body, content_type, name, head)
-
-
Utils.normalize_params(@params, name, data) unless data.nil?
-
-
# break if we're at the end of a buffer, but not if it is the end of a field
-
break if (@buf.empty? && $1 != EOL) || @content_length == -1
-
end
-
-
@io.rewind
-
-
@params.to_params_hash
-
end
-
-
1
private
-
1
def setup_parse
-
101
return false unless @env['CONTENT_TYPE'] =~ MULTIPART
-
-
@boundary = "--#{$1}"
-
-
@buf = ""
-
@params = Utils::KeySpaceConstrainedParams.new
-
-
@io = @env['rack.input']
-
@io.rewind
-
-
@boundary_size = Utils.bytesize(@boundary) + EOL.size
-
-
if @content_length = @env['CONTENT_LENGTH']
-
@content_length = @content_length.to_i
-
@content_length -= @boundary_size
-
end
-
true
-
end
-
-
1
def full_boundary
-
@boundary + EOL
-
end
-
-
1
def rx
-
@rx ||= /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
-
end
-
-
1
def fast_forward_to_first_boundary
-
loop do
-
content = @io.read(BUFSIZE)
-
raise EOFError, "bad content body" unless content
-
@buf << content
-
-
while @buf.gsub!(/\A([^\n]*\n)/, '')
-
read_buffer = $1
-
return if read_buffer == full_boundary
-
end
-
-
raise EOFError, "bad content body" if Utils.bytesize(@buf) >= BUFSIZE
-
end
-
end
-
-
1
def get_current_head_and_filename_and_content_type_and_name_and_body
-
head = nil
-
body = ''
-
filename = content_type = name = nil
-
content = nil
-
-
until head && @buf =~ rx
-
if !head && i = @buf.index(EOL+EOL)
-
head = @buf.slice!(0, i+2) # First \r\n
-
-
@buf.slice!(0, 2) # Second \r\n
-
-
content_type = head[MULTIPART_CONTENT_TYPE, 1]
-
name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1]
-
-
filename = get_filename(head)
-
-
if filename
-
body = Tempfile.new("RackMultipart")
-
body.binmode if body.respond_to?(:binmode)
-
end
-
-
next
-
end
-
-
# Save the read body part.
-
if head && (@boundary_size+4 < @buf.size)
-
body << @buf.slice!(0, @buf.size - (@boundary_size+4))
-
end
-
-
content = @io.read(@content_length && BUFSIZE >= @content_length ? @content_length : BUFSIZE)
-
raise EOFError, "bad content body" if content.nil? || content.empty?
-
-
@buf << content
-
@content_length -= content.size if @content_length
-
end
-
-
[head, filename, content_type, name, body]
-
end
-
-
1
def get_filename(head)
-
filename = nil
-
if head =~ RFC2183
-
filename = Hash[head.scan(DISPPARM)]['filename']
-
filename = $1 if filename and filename =~ /^"(.*)"$/
-
elsif head =~ BROKEN_QUOTED
-
filename = $1
-
elsif head =~ BROKEN_UNQUOTED
-
filename = $1
-
end
-
-
if filename && filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
-
filename = Utils.unescape(filename)
-
end
-
if filename && filename !~ /\\[^\\"]/
-
filename = filename.gsub(/\\(.)/, '\1')
-
end
-
filename
-
end
-
-
1
def get_data(filename, body, content_type, name, head)
-
data = nil
-
if filename == ""
-
# filename is blank which means no file has been selected
-
return data
-
elsif filename
-
body.rewind
-
-
# Take the basename of the upload's original filename.
-
# This handles the full Windows paths given by Internet Explorer
-
# (and perhaps other broken user agents) without affecting
-
# those which give the lone filename.
-
filename = filename.split(/[\/\\]/).last
-
-
data = {:filename => filename, :type => content_type,
-
:name => name, :tempfile => body, :head => head}
-
elsif !filename && content_type && body.is_a?(IO)
-
body.rewind
-
-
# Generic multipart cases, not coming from a form
-
data = {:type => content_type,
-
:name => name, :tempfile => body, :head => head}
-
else
-
data = body
-
end
-
-
[filename, data]
-
end
-
end
-
end
-
end
-
1
require 'active_support/backtrace_cleaner'
-
-
1
module Rails
-
1
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
-
1
APP_DIRS_PATTERN = /^\/?(app|config|lib|test)/
-
1
RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/
-
-
1
def initialize
-
1
super
-
1
add_filter { |line| line.sub("#{Rails.root}/", '') }
-
1
add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, '') }
-
1
add_filter { |line| line.sub('./', '/') } # for tests
-
-
1
add_gem_filters
-
1
add_silencer { |line| line !~ APP_DIRS_PATTERN }
-
end
-
-
1
private
-
1
def add_gem_filters
-
3
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
-
1
return if gems_paths.empty?
-
-
1
gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
-
1
add_filter { |line| line.sub(gems_regexp, '\2 (\3) \4') }
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/helpers"
-
1
require 'stringio'
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# RSpec's built-in formatters are all subclasses of
-
# RSpec::Core::Formatters::BaseTextFormatter.
-
#
-
# @see RSpec::Core::Formatters::BaseTextFormatter
-
# @see RSpec::Core::Reporter
-
# @see RSpec::Core::Formatters::Protocol
-
1
class BaseFormatter
-
# All formatters inheriting from this formatter will receive these
-
# notifications.
-
1
Formatters.register self, :start, :example_group_started, :close
-
1
attr_accessor :example_group
-
1
attr_reader :output
-
-
# @api public
-
# @param output [IO] the formatter output
-
# @see RSpec::Core::Formatters::Protocol#initialize
-
1
def initialize(output)
-
1
@output = output || StringIO.new
-
1
@example_group = nil
-
end
-
-
# @api public
-
#
-
# @param notification [StartNotification]
-
# @see RSpec::Core::Formatters::Protocol#start
-
1
def start(notification)
-
1
start_sync_output
-
1
@example_count = notification.count
-
end
-
-
# @api public
-
#
-
# @param notification [GroupNotification] containing example_group
-
# subclass of `RSpec::Core::ExampleGroup`
-
# @see RSpec::Core::Formatters::Protocol#example_group_started
-
1
def example_group_started(notification)
-
134
@example_group = notification.group
-
end
-
-
# @api public
-
#
-
# @param _notification [NullNotification] (Ignored)
-
# @see RSpec::Core::Formatters::Protocol#close
-
1
def close(_notification)
-
restore_sync_output
-
end
-
-
1
private
-
-
1
def start_sync_output
-
1
@old_sync, output.sync = output.sync, true if output_supports_sync
-
end
-
-
1
def restore_sync_output
-
output.sync = @old_sync if output_supports_sync && !output.closed?
-
end
-
-
1
def output_supports_sync
-
1
output.respond_to?(:sync=)
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_formatter"
-
1
RSpec::Support.require_rspec_core "formatters/console_codes"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# Base for all of RSpec's built-in formatters. See
-
# RSpec::Core::Formatters::BaseFormatter to learn more about all of the
-
# methods called by the reporter.
-
#
-
# @see RSpec::Core::Formatters::BaseFormatter
-
# @see RSpec::Core::Reporter
-
1
class BaseTextFormatter < BaseFormatter
-
1
Formatters.register self,
-
:message, :dump_summary, :dump_failures, :dump_pending, :seed
-
-
# @api public
-
#
-
# Used by the reporter to send messages to the output stream.
-
#
-
# @param notification [MessageNotification] containing message
-
1
def message(notification)
-
output.puts notification.message
-
end
-
-
# @api public
-
#
-
# Dumps detailed information about each example failure.
-
#
-
# @param notification [NullNotification]
-
1
def dump_failures(notification)
-
1
return if notification.failure_notifications.empty?
-
output.puts notification.fully_formatted_failed_examples
-
end
-
-
# @api public
-
#
-
# This method is invoked after the dumping of examples and failures.
-
# Each parameter is assigned to a corresponding attribute.
-
#
-
# @param summary [SummaryNotification] containing duration,
-
# example_count, failure_count and pending_count
-
1
def dump_summary(summary)
-
1
output.puts summary.fully_formatted
-
end
-
-
# @private
-
1
def dump_pending(notification)
-
1
return if notification.pending_examples.empty?
-
output.puts notification.fully_formatted_pending_examples
-
end
-
-
# @private
-
1
def seed(notification)
-
2
return unless notification.seed_used?
-
output.puts notification.fully_formatted
-
end
-
-
# @api public
-
#
-
# Invoked at the very end, `close` allows the formatter to clean
-
# up resources, e.g. open streams, etc.
-
#
-
# @param _notification [NullNotification] (Ignored)
-
1
def close(_notification)
-
1
return unless IO === output
-
1
return if output.closed?
-
-
1
output.puts
-
-
1
output.flush
-
1
output.close unless output == $stdout
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# ConsoleCodes provides helpers for formatting console output
-
# with ANSI codes, e.g. color's and bold.
-
1
module ConsoleCodes
-
# @private
-
1
VT100_CODES =
-
{
-
:black => 30,
-
:red => 31,
-
:green => 32,
-
:yellow => 33,
-
:blue => 34,
-
:magenta => 35,
-
:cyan => 36,
-
:white => 37,
-
:bold => 1,
-
}
-
# @private
-
1
VT100_CODE_VALUES = VT100_CODES.invert
-
-
1
module_function
-
-
# @private
-
1
CONFIG_COLORS_TO_METHODS = Configuration.instance_methods.grep(/_color\z/).inject({}) do |hash, method|
-
6
hash[method.to_s.sub(/_color\z/, '').to_sym] = method
-
6
hash
-
end
-
-
# Fetches the correct code for the supplied symbol, or checks
-
# that a code is valid. Defaults to white (37).
-
#
-
# @param code_or_symbol [Symbol, Fixnum] Symbol or code to check
-
# @return [Fixnum] a console code
-
1
def console_code_for(code_or_symbol)
-
353
if (config_method = CONFIG_COLORS_TO_METHODS[code_or_symbol])
-
176
console_code_for RSpec.configuration.__send__(config_method)
-
177
elsif VT100_CODE_VALUES.key?(code_or_symbol)
-
code_or_symbol
-
else
-
177
VT100_CODES.fetch(code_or_symbol) do
-
console_code_for(:white)
-
end
-
end
-
end
-
-
# Wraps a piece of text in ANSI codes with the supplied code. Will
-
# only apply the control code if `RSpec.configuration.color_enabled?`
-
# returns true.
-
#
-
# @param text [String] the text to wrap
-
# @param code_or_symbol [Symbol, Fixnum] the desired control code
-
# @return [String] the wrapped text
-
1
def wrap(text, code_or_symbol)
-
177
if RSpec.configuration.color_enabled?
-
177
"\e[#{console_code_for(code_or_symbol)}m#{text}\e[0m"
-
else
-
text
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_text_formatter"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @private
-
1
class ProgressFormatter < BaseTextFormatter
-
1
Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
-
-
1
def example_passed(_notification)
-
176
output.print ConsoleCodes.wrap('.', :success)
-
end
-
-
1
def example_pending(_notification)
-
output.print ConsoleCodes.wrap('*', :pending)
-
end
-
-
1
def example_failed(_notification)
-
output.print ConsoleCodes.wrap('F', :failure)
-
end
-
-
1
def start_dump(_notification)
-
1
output.puts
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `change`.
-
# Not intended to be instantiated directly.
-
1
class Change < BaseMatcher
-
# @api public
-
# Specifies the delta of the expected change.
-
1
def by(expected_delta)
-
11
ChangeRelatively.new(@change_details, expected_delta, :by) do |actual_delta|
-
11
values_match?(expected_delta, actual_delta)
-
end
-
end
-
-
# @api public
-
# Specifies a minimum delta of the expected change.
-
1
def by_at_least(minimum)
-
ChangeRelatively.new(@change_details, minimum, :by_at_least) do |actual_delta|
-
actual_delta >= minimum
-
end
-
end
-
-
# @api public
-
# Specifies a maximum delta of the expected change.
-
1
def by_at_most(maximum)
-
ChangeRelatively.new(@change_details, maximum, :by_at_most) do |actual_delta|
-
actual_delta <= maximum
-
end
-
end
-
-
# @api public
-
# Specifies the new value you expect.
-
1
def to(value)
-
ChangeToValue.new(@change_details, value)
-
end
-
-
# @api public
-
# Specifies the original value.
-
1
def from(value)
-
ChangeFromValue.new(@change_details, value)
-
end
-
-
# @private
-
1
def matches?(event_proc)
-
3
@event_proc = event_proc
-
3
return false unless Proc === event_proc
-
3
raise_block_syntax_error if block_given?
-
3
@change_details.perform_change(event_proc)
-
3
@change_details.changed?
-
end
-
-
1
def does_not_match?(event_proc)
-
3
raise_block_syntax_error if block_given?
-
3
!matches?(event_proc) && Proc === event_proc
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected #{@change_details.message} to have changed, but #{positive_failure_reason}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{@change_details.message} not to have changed, but #{negative_failure_reason}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"change #{@change_details.message}"
-
end
-
-
# @private
-
1
def supports_block_expectations?
-
3
true
-
end
-
-
1
private
-
-
1
def initialize(receiver=nil, message=nil, &block)
-
14
@change_details = ChangeDetails.new(receiver, message, &block)
-
end
-
-
1
def raise_block_syntax_error
-
raise SyntaxError, "The block passed to the `change` matcher must " \
-
"use `{ ... }` instead of do/end"
-
end
-
-
1
def positive_failure_reason
-
return "was not given a block" unless Proc === @event_proc
-
"is still #{description_of @change_details.actual_before}"
-
end
-
-
1
def negative_failure_reason
-
return "was not given a block" unless Proc === @event_proc
-
"did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}"
-
end
-
end
-
-
# Used to specify a relative change.
-
# @api private
-
1
class ChangeRelatively < BaseMatcher
-
1
def initialize(change_details, expected_delta, relativity, &comparer)
-
11
@change_details = change_details
-
11
@expected_delta = expected_delta
-
11
@relativity = relativity
-
11
@comparer = comparer
-
end
-
-
# @private
-
1
def failure_message
-
"expected #{@change_details.message} to have changed #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}, but #{failure_reason}"
-
end
-
-
# @private
-
1
def matches?(event_proc)
-
11
@event_proc = event_proc
-
11
return false unless Proc === event_proc
-
11
@change_details.perform_change(event_proc)
-
11
@comparer.call(@change_details.actual_delta)
-
end
-
-
# @private
-
1
def does_not_match?(_event_proc)
-
raise NotImplementedError, "`expect { }.not_to change { }.#{@relativity}()` is not supported"
-
end
-
-
# @private
-
1
def description
-
"change #{@change_details.message} #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}"
-
end
-
-
# @private
-
1
def supports_block_expectations?
-
11
true
-
end
-
-
1
private
-
-
1
def failure_reason
-
return "was not given a block" unless Proc === @event_proc
-
"was changed by #{description_of @change_details.actual_delta}"
-
end
-
end
-
-
# @api private
-
# Base class for specifying a change from and/or to specific values.
-
1
class SpecificValuesChange < BaseMatcher
-
# @private
-
1
MATCH_ANYTHING = ::Object.ancestors.last
-
-
1
def initialize(change_details, from, to)
-
@change_details = change_details
-
@expected_before = from
-
@expected_after = to
-
end
-
-
# @private
-
1
def matches?(event_proc)
-
@event_proc = event_proc
-
return false unless Proc === event_proc
-
@change_details.perform_change(event_proc)
-
@change_details.changed? && matches_before? && matches_after?
-
end
-
-
# @private
-
1
def description
-
"change #{@change_details.message} #{change_description}"
-
end
-
-
# @private
-
1
def failure_message
-
return not_given_a_block_failure unless Proc === @event_proc
-
return before_value_failure unless matches_before?
-
return did_not_change_failure unless @change_details.changed?
-
after_value_failure
-
end
-
-
# @private
-
1
def supports_block_expectations?
-
true
-
end
-
-
1
private
-
-
1
def matches_before?
-
values_match?(@expected_before, @change_details.actual_before)
-
end
-
-
1
def matches_after?
-
values_match?(@expected_after, @change_details.actual_after)
-
end
-
-
1
def before_value_failure
-
"expected #{@change_details.message} to have initially been #{description_of @expected_before}, but was #{description_of @change_details.actual_before}"
-
end
-
-
1
def after_value_failure
-
"expected #{@change_details.message} to have changed to #{description_of @expected_after}, but is now #{description_of @change_details.actual_after}"
-
end
-
-
1
def did_not_change_failure
-
"expected #{@change_details.message} to have changed #{change_description}, but did not change"
-
end
-
-
1
def did_change_failure
-
"expected #{@change_details.message} not to have changed, but did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}"
-
end
-
-
1
def not_given_a_block_failure
-
"expected #{@change_details.message} to have changed #{change_description}, but was not given a block"
-
end
-
end
-
-
# @api private
-
# Used to specify a change from a specific value
-
# (and, optionally, to a specific value).
-
1
class ChangeFromValue < SpecificValuesChange
-
1
def initialize(change_details, expected_before)
-
@description_suffix = nil
-
super(change_details, expected_before, MATCH_ANYTHING)
-
end
-
-
# @api public
-
# Specifies the new value you expect.
-
1
def to(value)
-
@expected_after = value
-
@description_suffix = " to #{description_of value}"
-
self
-
end
-
-
# @private
-
1
def does_not_match?(event_proc)
-
if @description_suffix
-
raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
-
end
-
-
@event_proc = event_proc
-
return false unless Proc === event_proc
-
@change_details.perform_change(event_proc)
-
!@change_details.changed? && matches_before?
-
end
-
-
# @private
-
1
def failure_message_when_negated
-
return not_given_a_block_failure unless Proc === @event_proc
-
return before_value_failure unless matches_before?
-
did_change_failure
-
end
-
-
1
private
-
-
1
def change_description
-
"from #{description_of @expected_before}#{@description_suffix}"
-
end
-
end
-
-
# @api private
-
# Used to specify a change to a specific value
-
# (and, optionally, from a specific value).
-
1
class ChangeToValue < SpecificValuesChange
-
1
def initialize(change_details, expected_after)
-
@description_suffix = nil
-
super(change_details, MATCH_ANYTHING, expected_after)
-
end
-
-
# @api public
-
# Specifies the original value.
-
1
def from(value)
-
@expected_before = value
-
@description_suffix = " from #{description_of value}"
-
self
-
end
-
-
# @private
-
1
def does_not_match?(_event_proc)
-
raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
-
end
-
-
1
private
-
-
1
def change_description
-
"to #{description_of @expected_after}#{@description_suffix}"
-
end
-
end
-
-
# @private
-
# Encapsulates the details of the before/after values.
-
1
class ChangeDetails
-
1
attr_reader :message, :actual_before, :actual_after
-
-
1
def initialize(receiver=nil, message=nil, &block)
-
14
if receiver && !message
-
raise(
-
ArgumentError,
-
"`change` requires either an object and message " \
-
"(`change(obj, :msg)`) or a block (`change { }`). " \
-
"You passed an object but no message."
-
)
-
end
-
14
@message = message ? "##{message}" : "result"
-
42
@value_proc = block || lambda { receiver.__send__(message) }
-
end
-
-
1
def perform_change(event_proc)
-
14
@actual_before = evaluate_value_proc
-
14
event_proc.call
-
14
@actual_after = evaluate_value_proc
-
end
-
-
1
def changed?
-
3
@actual_before != @actual_after
-
end
-
-
1
def actual_delta
-
11
@actual_after - @actual_before
-
end
-
-
1
private
-
-
1
def evaluate_value_proc
-
28
case val = @value_proc.call
-
when IO # enumerable, but we don't want to dup it.
-
val
-
when Enumerable, String
-
val.dup
-
else
-
28
val
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `eq`.
-
# Not intended to be instantiated directly.
-
1
class Eq < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
7
"eq #{expected_formatted}"
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
true
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
9
actual == expected
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `has_<predicate>`.
-
# Not intended to be instantiated directly.
-
1
class Has < BaseMatcher
-
1
def initialize(method_name, *args, &block)
-
105
@method_name, @args, @block = method_name, args, block
-
end
-
-
# @private
-
1
def matches?(actual, &block)
-
97
@actual = actual
-
97
@block ||= block
-
97
predicate_accessible? && predicate_matches?
-
end
-
-
# @private
-
1
def does_not_match?(actual, &block)
-
8
@actual = actual
-
8
@block ||= block
-
8
predicate_accessible? && !predicate_matches?
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
validity_message || "expected ##{predicate}#{failure_message_args_description} to return true, got false"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
validity_message || "expected ##{predicate}#{failure_message_args_description} to return false, got true"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
65
[method_description, args_description].compact.join(' ')
-
end
-
-
1
private
-
-
1
def predicate_accessible?
-
105
!private_predicate? && predicate_exists?
-
end
-
-
# support 1.8.7, evaluate once at load time for performance
-
1
if String === methods.first
-
# :nocov:
-
skipped
def private_predicate?
-
skipped
@actual.private_methods.include? predicate.to_s
-
skipped
end
-
# :nocov:
-
else
-
1
def private_predicate?
-
105
@actual.private_methods.include? predicate
-
end
-
end
-
-
1
def predicate_exists?
-
105
@actual.respond_to? predicate
-
end
-
-
1
def predicate_matches?
-
105
@actual.__send__(predicate, *@args, &@block)
-
end
-
-
1
def predicate
-
# On 1.9, there appears to be a bug where String#match can return `false`
-
# rather than the match data object. Changing to Regex#match appears to
-
# work around this bug. For an example of this bug, see:
-
# https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
-
315
@predicate ||= :"has_#{Matchers::HAS_REGEX.match(@method_name.to_s).captures.first}?"
-
end
-
-
1
def method_description
-
65
@method_name.to_s.gsub('_', ' ')
-
end
-
-
1
def args_description
-
65
return nil if @args.empty?
-
147
@args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(', ')
-
end
-
-
1
def failure_message_args_description
-
desc = args_description
-
"(#{desc})" if desc
-
end
-
-
1
def validity_message
-
if private_predicate?
-
"expected #{@actual} to respond to `#{predicate}` but `#{predicate}` is a private method"
-
elsif !predicate_exists?
-
"expected #{@actual} to respond to `#{predicate}`"
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `include`.
-
# Not intended to be instantiated directly.
-
1
class Include < BaseMatcher
-
1
def initialize(*expected)
-
9
@expected = expected
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def matches?(actual)
-
14
perform_match(actual) { |v| v }
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def does_not_match?(actual)
-
4
perform_match(actual) { |v| !v }
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
7
improve_hash_formatting("include#{readable_list_of(expected)}")
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
format_failure_message("to") { super }
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
format_failure_message("not to") { super }
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
!diff_would_wrongly_highlight_matched_item?
-
end
-
-
1
private
-
-
1
def format_failure_message(preposition)
-
if actual.respond_to?(:include?)
-
improve_hash_formatting("expected #{description_of @actual} #{preposition} include#{readable_list_of @divergent_items}")
-
else
-
improve_hash_formatting(yield) + ", but it does not respond to `include?`"
-
end
-
end
-
-
1
def readable_list_of(items)
-
7
described_items = surface_descriptions_in(items)
-
14
if described_items.all? { |item| item.is_a?(Hash) }
-
" #{described_items.inject(:merge).inspect}"
-
else
-
7
EnglishPhrasing.list(described_items)
-
end
-
end
-
-
1
def perform_match(actual, &block)
-
9
@actual = actual
-
9
@divergent_items = excluded_from_actual(&block)
-
9
actual.respond_to?(:include?) && @divergent_items.empty?
-
end
-
-
1
def excluded_from_actual
-
9
return [] unless @actual.respond_to?(:include?)
-
-
9
expected.inject([]) do |memo, expected_item|
-
9
if comparing_hash_to_a_subset?(expected_item)
-
expected_item.each do |(key, value)|
-
memo << { key => value } unless yield actual_hash_includes?(key, value)
-
end
-
elsif comparing_hash_keys?(expected_item)
-
memo << expected_item unless yield actual_hash_has_key?(expected_item)
-
else
-
9
memo << expected_item unless yield actual_collection_includes?(expected_item)
-
end
-
9
memo
-
end
-
end
-
-
1
def comparing_hash_to_a_subset?(expected_item)
-
9
actual.is_a?(Hash) && expected_item.is_a?(Hash)
-
end
-
-
1
def actual_hash_includes?(expected_key, expected_value)
-
actual_value = actual.fetch(expected_key) { return false }
-
values_match?(expected_value, actual_value)
-
end
-
-
1
def comparing_hash_keys?(expected_item)
-
9
actual.is_a?(Hash) && !expected_item.is_a?(Hash)
-
end
-
-
1
def actual_hash_has_key?(expected_key)
-
# We check `key?` first for perf:
-
# `key?` is O(1), but `any?` is O(N).
-
actual.key?(expected_key) ||
-
actual.keys.any? { |key| values_match?(expected_key, key) }
-
end
-
-
1
def actual_collection_includes?(expected_item)
-
9
return true if actual.include?(expected_item)
-
-
# String lacks an `any?` method...
-
2
return false unless actual.respond_to?(:any?)
-
-
7
actual.any? { |value| values_match?(expected_item, value) }
-
end
-
-
1
def diff_would_wrongly_highlight_matched_item?
-
return false unless actual.is_a?(String) && expected.is_a?(Array)
-
-
lines = actual.split("\n")
-
expected.any? do |str|
-
actual.include?(str) && lines.none? { |line| line == str }
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `match`.
-
# Not intended to be instantiated directly.
-
1
class Match < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def description
-
1
"match #{surface_descriptions_in(expected).inspect}"
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
true
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
4
return true if values_match?(expected, actual)
-
2
return false unless can_safely_call_match?(expected, actual)
-
2
actual.match(expected)
-
end
-
-
1
def can_safely_call_match?(expected, actual)
-
2
return false unless actual.respond_to?(:match)
-
-
!(RSpec::Matchers.is_a_matcher?(expected) &&
-
2
(String === actual || Regexp === actual))
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "method_signature_verifier"
-
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `respond_to`.
-
# Not intended to be instantiated directly.
-
1
class RespondTo < BaseMatcher
-
1
def initialize(*names)
-
27
@names = names
-
27
@expected_arity = nil
-
end
-
-
# @api public
-
# Specifies the number of expected arguments.
-
#
-
# @example
-
# expect(obj).to respond_to(:message).with(3).arguments
-
1
def with(n)
-
@expected_arity = n
-
self
-
end
-
-
# @api public
-
# No-op. Intended to be used as syntactic sugar when using `with`.
-
#
-
# @example
-
# expect(obj).to respond_to(:message).with(3).arguments
-
1
def argument
-
self
-
end
-
1
alias :arguments :argument
-
-
# @private
-
1
def matches?(actual)
-
27
find_failing_method_names(actual, :reject).empty?
-
end
-
-
# @private
-
1
def does_not_match?(actual)
-
find_failing_method_names(actual, :select).empty?
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected #{actual_formatted} to respond to #{@failing_method_names.map { |name| description_of(name) }.join(', ')}#{with_arity}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
failure_message.sub(/to respond to/, 'not to respond to')
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
27
"respond to #{pp_names}#{with_arity}"
-
end
-
-
1
private
-
-
1
def find_failing_method_names(actual, filter_method)
-
27
@actual = actual
-
27
@failing_method_names = @names.__send__(filter_method) do |name|
-
27
@actual.respond_to?(name) && matches_arity?(actual, name)
-
end
-
end
-
-
1
def matches_arity?(actual, name)
-
27
return true unless @expected_arity
-
-
signature = Support::MethodSignature.new(actual.method(name))
-
Support::StrictSignatureVerifier.new(signature, Array.new(@expected_arity)).valid?
-
end
-
-
1
def with_arity
-
27
return "" unless @expected_arity
-
" with #{@expected_arity} argument#{@expected_arity == 1 ? '' : 's'}"
-
end
-
-
1
def pp_names
-
27
@names.length == 1 ? "##{@names.first}" : description_of(@names)
-
end
-
end
-
end
-
end
-
end
-
1
require 'sass'
-
-
1
module Sprockets
-
1
class SassCacheStore < ::Sass::CacheStores::Base
-
1
attr_reader :environment
-
-
1
def initialize(environment)
-
4
@environment = environment
-
end
-
-
1
def _store(key, version, sha, contents)
-
36
environment.cache_set("sass/#{key}", {:version => version, :sha => sha, :contents => contents})
-
end
-
-
1
def _retrieve(key, version, sha)
-
40
if obj = environment.cache_get("sass/#{key}")
-
4
return unless obj[:version] == version
-
4
return unless obj[:sha] == sha
-
4
obj[:contents]
-
else
-
nil
-
end
-
end
-
-
1
def path_to(key)
-
key
-
end
-
end
-
end
-
1
module TZInfo
-
1
module Definitions
-
1
module Asia
-
1
module Tokyo
-
1
include TimezoneDefinition
-
-
1
timezone 'Asia/Tokyo' do |tz|
-
1
tz.offset :o0, 33539, 0, :LMT
-
1
tz.offset :o1, 32400, 0, :JST
-
1
tz.offset :o2, 32400, 0, :JCST
-
1
tz.offset :o3, 32400, 3600, :JDT
-
-
1
tz.transition 1887, 12, :o1, 19285097, 8
-
1
tz.transition 1895, 12, :o2, 19308473, 8
-
1
tz.transition 1937, 9, :o1, 19430457, 8
-
1
tz.transition 1948, 5, :o3, 58384157, 24
-
1
tz.transition 1948, 9, :o1, 14596831, 6
-
1
tz.transition 1949, 4, :o3, 58392221, 24
-
1
tz.transition 1949, 9, :o1, 14599015, 6
-
1
tz.transition 1950, 5, :o3, 58401797, 24
-
1
tz.transition 1950, 9, :o1, 14601199, 6
-
1
tz.transition 1951, 5, :o3, 58410533, 24
-
1
tz.transition 1951, 9, :o1, 14603383, 6
-
end
-
end
-
end
-
end
-
end
-
1
require 'will_paginate/per_page'
-
1
require 'will_paginate/page_number'
-
1
require 'will_paginate/collection'
-
1
require 'active_record'
-
-
1
module WillPaginate
-
# = Paginating finders for ActiveRecord models
-
#
-
# WillPaginate adds +paginate+, +per_page+ and other methods to
-
# ActiveRecord::Base class methods and associations.
-
#
-
# In short, paginating finders are equivalent to ActiveRecord finders; the
-
# only difference is that we start with "paginate" instead of "find" and
-
# that <tt>:page</tt> is required parameter:
-
#
-
# @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC'
-
#
-
1
module ActiveRecord
-
# makes a Relation look like WillPaginate::Collection
-
1
module RelationMethods
-
1
include WillPaginate::CollectionMethods
-
-
1
attr_accessor :current_page
-
1
attr_writer :total_entries, :wp_count_options
-
-
1
def per_page(value = nil)
-
66
if value.nil? then limit_value
-
else limit(value)
-
end
-
end
-
-
# TODO: solve with less relation clones and code dups
-
1
def limit(num)
-
rel = super
-
if rel.current_page
-
rel.offset rel.current_page.to_offset(rel.limit_value).to_i
-
else
-
rel
-
end
-
end
-
-
# dirty hack to enable `first` after `limit` behavior above
-
1
def first(*args)
-
if current_page
-
rel = clone
-
rel.current_page = nil
-
rel.first(*args)
-
else
-
super
-
end
-
end
-
-
# fix for Rails 3.0
-
1
def find_last
-
if !loaded? and offset_value || limit_value
-
@last ||= to_a.last
-
else
-
super
-
end
-
end
-
-
1
def offset(value = nil)
-
123
if value.nil? then offset_value
-
123
else super(value)
-
end
-
end
-
-
1
def total_entries
-
@total_entries ||= begin
-
50
if loaded? and size < limit_value and (current_page == 1 or size > 0)
-
36
offset_value + size
-
else
-
14
@total_entries_queried = true
-
14
result = count
-
14
result = result.size if result.respond_to?(:size) and !result.is_a?(Integer)
-
14
result
-
end
-
135
end
-
end
-
-
1
def count
-
180
if limit_value
-
90
excluded = [:order, :limit, :offset]
-
90
excluded << :includes unless eager_loading?
-
90
rel = self.except(*excluded)
-
# TODO: hack. decide whether to keep
-
90
rel = rel.apply_finder_options(@wp_count_options) if defined? @wp_count_options
-
90
rel.count
-
else
-
90
super
-
end
-
end
-
-
# workaround for Active Record 3.0
-
1
def size
-
73
if !loaded? and limit_value and group_values.empty?
-
[super, limit_value].min
-
else
-
73
super
-
end
-
end
-
-
# overloaded to be pagination-aware
-
1
def empty?
-
82
if !loaded? and offset_value
-
76
result = count
-
76
result = result.size if result.respond_to?(:size) and !result.is_a?(Integer)
-
76
result <= offset_value
-
else
-
6
super
-
end
-
end
-
-
1
def clone
-
213
copy_will_paginate_data super
-
end
-
-
# workaround for Active Record 3.0
-
1
def scoped(options = nil)
-
copy_will_paginate_data super
-
end
-
-
1
def to_a
-
86
if current_page.nil? then super # workaround for Active Record 3.0
-
else
-
56
::WillPaginate::Collection.create(current_page, limit_value) do |col|
-
56
col.replace super
-
56
col.total_entries ||= total_entries
-
end
-
end
-
end
-
-
1
private
-
-
1
def copy_will_paginate_data(other)
-
213
other.current_page = current_page unless other.current_page
-
213
other.total_entries = nil if defined? @total_entries_queried
-
213
other.wp_count_options = @wp_count_options if defined? @wp_count_options
-
213
other
-
end
-
end
-
-
1
module Pagination
-
1
def paginate(options)
-
123
options = options.dup
-
123
pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" }
-
123
per_page = options.delete(:per_page) || self.per_page
-
123
total = options.delete(:total_entries)
-
-
123
count_options = options.delete(:count)
-
123
options.delete(:page)
-
-
123
rel = limit(per_page.to_i).page(pagenum)
-
123
rel = rel.apply_finder_options(options) if options.any?
-
123
rel.wp_count_options = count_options if count_options
-
123
rel.total_entries = total.to_i unless total.blank?
-
123
rel
-
end
-
-
1
def page(num)
-
123
rel = if ::ActiveRecord::Relation === self
-
123
self
-
elsif !defined?(::ActiveRecord::Scoping) or ::ActiveRecord::Scoping::ClassMethods.method_defined? :with_scope
-
# Active Record 3
-
scoped
-
else
-
# Active Record 4
-
all
-
end
-
-
123
rel = rel.extending(RelationMethods)
-
123
pagenum = ::WillPaginate::PageNumber(num.nil? ? 1 : num)
-
123
per_page = rel.limit_value || self.per_page
-
123
rel = rel.offset(pagenum.to_offset(per_page).to_i)
-
123
rel = rel.limit(per_page) unless rel.limit_value
-
123
rel.current_page = pagenum
-
123
rel
-
end
-
end
-
-
1
module BaseMethods
-
# Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
-
# based on the params otherwise used by paginating finds: +page+ and
-
# +per_page+.
-
#
-
# Example:
-
#
-
# @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
-
# :page => params[:page], :per_page => 3
-
#
-
# A query for counting rows will automatically be generated if you don't
-
# supply <tt>:total_entries</tt>. If you experience problems with this
-
# generated SQL, you might want to perform the count manually in your
-
# application.
-
#
-
1
def paginate_by_sql(sql, options)
-
pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" } || 1
-
per_page = options[:per_page] || self.per_page
-
total = options[:total_entries]
-
-
WillPaginate::Collection.create(pagenum, per_page, total) do |pager|
-
query = sanitize_sql(sql.dup)
-
original_query = query.dup
-
oracle = self.connection.adapter_name =~ /^(oracle|oci$)/i
-
-
# add limit, offset
-
if oracle
-
query = <<-SQL
-
SELECT * FROM (
-
SELECT rownum rnum, a.* FROM (#{query}) a
-
WHERE rownum <= #{pager.offset + pager.per_page}
-
) WHERE rnum >= #{pager.offset}
-
SQL
-
else
-
query << " LIMIT #{pager.per_page} OFFSET #{pager.offset}"
-
end
-
-
# perfom the find
-
pager.replace find_by_sql(query)
-
-
unless pager.total_entries
-
count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s.]+$/mi, ''
-
count_query = "SELECT COUNT(*) FROM (#{count_query})"
-
count_query << ' AS count_table' unless oracle
-
# perform the count query
-
pager.total_entries = count_by_sql(count_query)
-
end
-
end
-
end
-
end
-
-
# mix everything into Active Record
-
1
::ActiveRecord::Base.extend PerPage
-
1
::ActiveRecord::Base.extend Pagination
-
1
::ActiveRecord::Base.extend BaseMethods
-
-
1
klasses = [::ActiveRecord::Relation]
-
1
if defined? ::ActiveRecord::Associations::CollectionProxy
-
1
klasses << ::ActiveRecord::Associations::CollectionProxy
-
else
-
klasses << ::ActiveRecord::Associations::AssociationCollection
-
end
-
-
# support pagination on associations and scopes
-
3
klasses.each { |klass| klass.send(:include, Pagination) }
-
end
-
end